diff --git a/submit/README.md b/submit/README.md new file mode 100644 index 0000000..a46ee38 --- /dev/null +++ b/submit/README.md @@ -0,0 +1,82 @@ +# E题最小提交材料 + +本目录只保留原题“四、结果与提交说明”中要求随附件提交的材料。特征文件、代码、模型参数与预测 CSV 合计 **47.19 MB**,低于 50 MB。官方原始附件由赛题另行提供,不重复打包;竞赛论文中的方法、实验表格和分析按题目要求写入论文正文。 + +## 提交内容 + +| 题目 | 文件 | 用途 | +|---|---|---| +| Q1 | `final/output/q1/features_v2/` | 100 条自生成多模态时序特征 `.npz`;附 `manifest_q1.jsonl`、`feature_manifest.json`、100 行样本汇总和 300 行模态汇总,用于追溯样本、维度与有效时长 | +| Q2 | `final/q2/math/`、`final/model/`、`final/adapter/` | C0–C7 数学方案的训练、数据处理、附件 3 推理和统一未对齐输入接口代码;随包参数对应验证集选定的 C6 | +| Q2 | `final/experiments/q2/unaligned_math_all_b128/` | C6 选定模型权重、结构化填补器、训练集预处理统计、验证集校准与运行配置 | +| Q2 | `final/output/q2/attachment3_predictions.csv` | 附件 3 的 30 条无标签预测 | +| Q3 | `final/q3/ati_ho/`、`final/q3/run_experiments.py`、`final/q2/deep_learning/q2/`、相关 `final/model/` 与 `final/adapter/` | ATI–HO 训练、预测、解释与局部证据计算代码;含运行说明 | +| Q3 | `final/experiments/q3/ati_ho/`、`final/experiments/q2/unaligned_deep_two_b128/unaligned_50_robust_stats.npz` | 验证集选定的 ATI–HO A0 三种子权重、配置和推理所需缩放器 | +| Q3 | `final/output/q3/ati_ho/attachment4_predictions.csv`、`attachment4_explanations.csv`、`attachment4_local_evidence.csv` | 附件 4 的全量预测、模态作用解释和局部证据位置(20 条样本、600 条局部证据记录) | + +未放入提交目录的训练日志、消融表、Bootstrap 表、错误归因表和历史方案输出不属于第四小节要求的附件。模型比较、验证结果与分析应呈现在论文正文中。 + +## 运行环境与数据位置 + +使用 Python 3.11 或更新版本。安装依赖: + +```bash +python -m venv .venv +source .venv/bin/activate # Windows PowerShell: .venv\Scripts\Activate.ps1 +python -m pip install -r requirements.txt +``` + +GPU 运行时,请安装与本机 CUDA 驱动匹配的 PyTorch;没有 GPU 时可使用 CPU,但训练和局部解释会更慢。附件 3 的文本重建会调用 `google-bert/bert-base-uncased`;首次运行需能下载该模型,或提前放入 Hugging Face 缓存。 + +将官方数据根目录放到任意位置,并设置 `FINAL_DATA_DIR` 指向该根目录。目录中需有: + +- `附件2-数据集特征文件/unaligned_50.pkl` +- `附件3-模态缺失特征样本/未对齐版本/` +- `附件4-可解释专项视频样本与特征文件/附件4-可解释专项视频样本与特征文件/未对齐版本/` + +Windows PowerShell 示例: + +```powershell +$env:FINAL_DATA_DIR = "D:/E题数据" +``` + +Linux/WSL 示例: + +```bash +export FINAL_DATA_DIR="/data/E题数据" +``` + +## 重新生成专项预测文件 + +从 `submit/` 目录运行。Q2 使用随包提供的 C6 权重、预处理统计和校准参数: + +```bash +python -m final.q2.math.predict_attachment3 --input-version unaligned_50 --device auto +``` + +Q3 使用随包提供的 A0 三种子权重,生成预测、模态解释和局部 Owen 证据 CSV: + +```bash +python -m final.q3.ati_ho.predict_attachment4 --device auto +``` + +输出分别写入 `final/output/q2/` 和 `final/output/q3/ati_ho/`。附件 3、附件 4 是无标签专项测试集,输出文件不包含测试指标。 + +## 从头训练 + +Q2 的 C0–C7 训练与比较代码在 `final/q2/math/train.py`。按本次 C6 运行配置执行: + +```bash +python -m final.q2.math.train --input-version unaligned_50 --epochs 12 --imputer-epochs 8 --batch-size 128 --patience 3 --seed 20260924 --device cuda --output-dir final/experiments/q2/unaligned_math_rerun +``` + +无 CUDA 时将 `--device cuda` 改为 `--device cpu`。训练完成后可用 `--results-dir final/experiments/q2/unaligned_math_rerun` 让 `predict_attachment3.py` 使用新权重。 + +Q3 完整训练会生成 ATI 消融与 EarlyConcat、MoFE 基线权重;之后运行完整验证和解释评估: + +```bash +python -m final.q3.ati_ho.train --phase all --device cuda --force +python -m final.q3.ati_ho.evaluate --device auto +``` + +完整训练需要附件 2 训练集以及随包提供的训练集缩放器。训练产生的比较模型和审计结果写入 `final/experiments/`,不需要作为当前最小附件提交。 \ No newline at end of file diff --git a/submit/final/__init__.py b/submit/final/__init__.py new file mode 100644 index 0000000..1f7fb9c --- /dev/null +++ b/submit/final/__init__.py @@ -0,0 +1 @@ +"""Consolidated Q1 alignment and Q2 models.""" diff --git a/submit/final/adapter/__init__.py b/submit/final/adapter/__init__.py new file mode 100644 index 0000000..d3c1e77 --- /dev/null +++ b/submit/final/adapter/__init__.py @@ -0,0 +1,17 @@ +"""Unified Q1 alignment interface for physical time and relative progress.""" + +from .core import ( + AlignedMultimodalSample, + AlignmentError, + ModalityProvenance, + Q1AlignmentAdapter, + adapt_official_split, +) + +__all__ = [ + "AlignedMultimodalSample", + "AlignmentError", + "ModalityProvenance", + "Q1AlignmentAdapter", + "adapt_official_split", +] diff --git a/submit/final/adapter/coordinates.py b/submit/final/adapter/coordinates.py new file mode 100644 index 0000000..13d98ec --- /dev/null +++ b/submit/final/adapter/coordinates.py @@ -0,0 +1,26 @@ +"""Coordinate constructors; no feature aggregation happens here.""" +from __future__ import annotations + +import numpy as np + + +def physical_targets(duration_s: float, steps: int) -> np.ndarray: + """K fixed bins spanning verified real media duration, in seconds.""" + duration = float(duration_s) + if not np.isfinite(duration) or duration <= 0 or steps < 1: + raise ValueError("physical targets require positive finite duration and steps") + edges = np.linspace(0.0, duration, steps + 1, dtype=np.float64) + return np.column_stack((edges[:-1], edges[1:])) + + +def relative_cells(length: int) -> np.ndarray: + """Ordered source cells on a unit progress axis, with no time claim.""" + if length < 1: + raise ValueError("relative source length must be positive") + left = np.arange(length, dtype=np.float64) / length + return np.column_stack((left, left + 1.0 / length)) + + +def relative_targets(steps: int) -> np.ndarray: + """K fixed cells on the same unit progress axis.""" + return relative_cells(steps) diff --git a/submit/final/adapter/core.py b/submit/final/adapter/core.py new file mode 100644 index 0000000..717e83e --- /dev/null +++ b/submit/final/adapter/core.py @@ -0,0 +1,238 @@ +"""Q1's common sample contract and evidence-based coordinate dispatch.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any + +import numpy as np +from scipy import sparse + +from .coordinates import physical_targets, relative_cells, relative_targets +from .projection import project_intervals + +MODALITIES = ("text", "audio", "vision") +DIMS = {"text": 768, "audio": 74, "vision": 35} +STEPS = {"text": 50, "audio": 500, "vision": 500} +K = 50 +VERSION = "q1-unified-1" + + +class AlignmentError(ValueError): + """The source cannot be assigned a defensible alignment coordinate.""" + + +@dataclass +class ModalityProvenance: + source_weights: sparse.csr_matrix + source_count: np.ndarray + first_source: np.ndarray + last_source: np.ndarray + source_span: int + original_source_length: int + reported_length: int | None + length_conflict: bool = False + tail_ambiguous: bool = False + observed_dimensions: np.ndarray | None = None + coverage_dimensions: np.ndarray | None = None + quality_available: np.ndarray | None = None + + +@dataclass +class AlignedMultimodalSample: + features: dict[str, np.ndarray] + observed: dict[str, np.ndarray] + coverage: dict[str, np.ndarray] + provenance: dict[str, ModalityProvenance] + metadata: dict[str, Any] + target_intervals: np.ndarray + quality_mean: dict[str, np.ndarray] = field(default_factory=dict) + quality_available_fraction: dict[str, np.ndarray] = field(default_factory=dict) + + def q2_arrays(self) -> tuple[dict[str, np.ndarray], np.ndarray]: + """The existing Q2 model input shapes, without changing that model.""" + return self.features, np.stack([self.observed[m] for m in MODALITIES], axis=-1) + + +@dataclass +class _Prepared: + values: np.ndarray + intervals: np.ndarray + observed: np.ndarray + quality: np.ndarray + reported_length: int | None + length_conflict: bool = False + tail_ambiguous: bool = False + quality_available: np.ndarray | None = None + + +def _relative_modality(record: dict[str, Any], name: str) -> _Prepared: + raw = np.asarray(record[name]) + if raw.shape != (STEPS[name], DIMS[name]) or not np.isfinite(raw).all(): + raise AlignmentError(f"{name}: expected finite {(STEPS[name], DIMS[name])}, got {raw.shape}") + if name == "text": + attention = np.asarray(record["attention_mask"], bool) + if attention.shape != (50,) or not np.array_equal(attention, np.arange(50) < int(attention.sum())): + raise AlignmentError("text attention mask must be a 50-position prefix") + length = int(attention.sum()) + if length < 1: + raise AlignmentError("empty text attention mask") + span = length + observed = attention[:span] & np.any(raw[:span] != 0, axis=1) + conflict = ambiguous = False + else: + length = int(record[f"{name}_length"]) + if length < 1 or length > STEPS[name]: + raise AlignmentError(f"{name}: invalid official length {length}") + nonzero = np.any(raw != 0, axis=1) + last = int(np.flatnonzero(nonzero)[-1]) + 1 if nonzero.any() else 0 + conflict = last > length + if name == "audio" and conflict: + raise AlignmentError("audio contains observed positions beyond audio_lengths") + span = max(length, last) + observed = nonzero[:span] + ambiguous = bool(conflict) + return _Prepared(raw[:span].astype(np.float32, copy=False), relative_cells(span), + observed, np.ones(span, np.float32), length, bool(conflict), bool(ambiguous), + np.zeros(span, bool)) + + +def _validate_physical(source: dict[str, Any]) -> tuple[float, dict[str, Any]]: + meta = source.get("_meta") + if not isinstance(meta, dict): + raise AlignmentError("physical mode requires stored Q1 metadata") + duration = float(meta.get("duration_s", float("nan"))) + if not np.isfinite(duration) or duration <= 0: + raise AlignmentError("physical mode requires a finite positive duration") + if meta.get("media", {}).get("status") != "ok" or not meta.get("source_video_sha256"): + raise AlignmentError("physical mode requires verified media status and source hash") + for name in MODALITIES: + if f"native_{name}_intervals" not in source: + raise AlignmentError(f"physical mode lacks {name} timestamps") + return duration, meta + + +class Q1AlignmentAdapter: + """Align either verified Q1 physical sources or official ordered sequences. + + `auto` uses evidence in the input contract only; tensor shape never decides + whether time is physical. A malformed physical source is an error rather + than a silent relative fallback. + """ + + def __init__(self, target_steps: int = K): + if target_steps < 1: + raise ValueError("target_steps must be positive") + self.target_steps = target_steps + + def align(self, source: dict[str, Any], mode: str = "auto") -> AlignedMultimodalSample: + if mode not in {"auto", "physical", "relative"}: + raise AlignmentError(f"unsupported coordinate mode: {mode}") + if mode == "auto": + if "_meta" in source or any(k.startswith("native_") for k in source): + mode = "physical" + elif source.get("sequence_order_verified") is True: + mode = "relative" + else: + raise AlignmentError("auto mode requires Q1 physical evidence or verified sequence order") + if mode == "physical": + from .source import native_arrays + + duration, meta = _validate_physical(source) + target = physical_targets(duration, self.target_steps) + prepared = {} + for name in MODALITIES: + values, observed, intervals, quality, available = native_arrays(source, name) + if np.asarray(intervals).shape != (len(values), 2) or np.any(np.asarray(intervals) < -1e-5) or np.any(np.asarray(intervals) > duration + 1e-5): + raise AlignmentError(f"{name}: physical timestamps outside media duration") + prepared[name] = _Prepared(values, intervals, observed, quality, len(values), + quality_available=available) + metadata = {"sample_id": meta.get("sample_id", f"{meta.get('video_id')}/{meta.get('clip_id')}"), + "coordinate_mode": "physical", "coordinate_unit": "seconds", + "physical_time_alignment": True, "duration_s": duration, + "source_video_sha256": meta["source_video_sha256"], + "dense_view": "views_sec_* (stored 0.1 s Q1 artifact)", + "quality_fields_available": {m: bool(np.asarray(prepared[m].quality_available).any()) for m in MODALITIES}} + else: + if source.get("sequence_order_verified") is not True: + raise AlignmentError("relative mode requires verified source order") + target = relative_targets(self.target_steps) + prepared = {name: _relative_modality(source, name) for name in MODALITIES} + metadata = {"sample_id": str(source.get("id", "")), "coordinate_mode": "relative", + "coordinate_unit": "normalized_progress", "physical_time_alignment": False, + "word_or_frame_timestamps_available": False, + "quality_fields_available": {m: False for m in MODALITIES}} + features = {} + observed = {} + coverage = {} + quality_mean = {} + quality_available_fraction = {} + provenance = {} + for name in MODALITIES: + item = prepared[name] + result = project_intervals(item.values, item.intervals, target, item.observed, item.quality, + item.quality_available) + features[name] = result.x + observed[name] = result.observed + coverage[name] = result.coverage + quality_mean[name] = result.quality_mean + quality_available_fraction[name] = result.quality_available_fraction + provenance[name] = ModalityProvenance(result.source_weights, result.source_count, + result.first_source, result.last_source, len(item.values), + len(source[name]) if mode == "relative" else len(item.values), item.reported_length, + item.length_conflict, item.tail_ambiguous, result.observed_dimensions, + result.coverage_dimensions, item.quality_available) + metadata.update({"target_steps": self.target_steps, "adapter_version": VERSION}) + return AlignedMultimodalSample(features, observed, coverage, provenance, metadata, target, + quality_mean, quality_available_fraction) + + def from_q1_sample(self, sample_id: str, feature_dir: Path | None = None) -> AlignedMultimodalSample: + from .source import FEATURE_DIR, load_sample + + return self.align(load_sample(sample_id, FEATURE_DIR if feature_dir is None else feature_dir), "auto") + + def from_unaligned_record(self, split: dict[str, Any], index: int) -> AlignedMultimodalSample: + """Build verified ordered input from one official unaligned pickle row.""" + attention = np.asarray(split["text_bert"][index, 1], bool) + raw_id = split["id"][index] + if isinstance(raw_id, bytes): + raw_id = raw_id.decode("utf-8", errors="replace") + record = {"id": str(raw_id), "sequence_order_verified": True, + "attention_mask": attention, + "text": split["text"][index], "audio": split["audio"][index], + "vision": split["vision"][index], + "audio_length": int(split["audio_lengths"][index]), + "vision_length": int(split["vision_lengths"][index])} + return self.align(record, "auto") + + +def adapt_official_split(split: dict[str, Any]) -> tuple[dict[str, np.ndarray], np.ndarray, dict[str, Any]]: + """Q2's batch bridge; all rows are produced through Q1AlignmentAdapter.""" + n = len(split["id"]) + output = {m: np.zeros((n, K, DIMS[m]), np.float32) for m in MODALITIES} + masks = np.zeros((n, K, len(MODALITIES)), bool) + conflicts = ambiguous = padding = 0 + coverage_sum = {m: 0.0 for m in MODALITIES} + observed_rows = {m: 0 for m in MODALITIES} + adapter = Q1AlignmentAdapter() + for i in range(n): + attention = np.asarray(split["text_bert"][i, 1], bool) + padding += int(np.count_nonzero(np.any(split["text"][i] != 0, axis=1) & ~attention)) + sample = adapter.from_unaligned_record(split, i) + for j, name in enumerate(MODALITIES): + output[name][i] = sample.features[name] + masks[i, :, j] = sample.observed[name] + coverage_sum[name] += float(sample.coverage[name].sum()) + observed_rows[name] += int(sample.observed[name].sum()) + conflicts += int(sample.provenance["vision"].length_conflict) + ambiguous += int(sample.provenance["vision"].tail_ambiguous) + audit = {"method": "shared_interval_overlap_on_normalized_progress", + "coordinate_mode": "relative", "physical_time_alignment": False, + "samples": n, "vision_length_conflict_samples": conflicts, + "vision_tail_ambiguous_samples": ambiguous, + "nonzero_text_rows_outside_attention": padding, + "observed_target_rows": observed_rows, + "mean_target_coverage": {m: coverage_sum[m] / (n * K) for m in MODALITIES}, + "quality_fields_available": False, + "word_or_frame_timestamps_available": False} + return output, masks, audit diff --git a/submit/final/adapter/projection.py b/submit/final/adapter/projection.py new file mode 100644 index 0000000..9b35e3d --- /dev/null +++ b/submit/final/adapter/projection.py @@ -0,0 +1,108 @@ +"""The sole interval overlap projection kernel used by both coordinate modes.""" +from __future__ import annotations + +from dataclasses import dataclass + +import numpy as np +from scipy import sparse + + +@dataclass +class Projection: + x: np.ndarray + observed: np.ndarray + coverage: np.ndarray + source_count: np.ndarray + first_source: np.ndarray + last_source: np.ndarray + source_weights: sparse.csr_matrix + observed_dimensions: np.ndarray + coverage_dimensions: np.ndarray + quality_mean: np.ndarray + quality_available_fraction: np.ndarray + + +def project_intervals( + values: np.ndarray, + source_intervals: np.ndarray, + target_intervals: np.ndarray, + observed: np.ndarray, + quality: np.ndarray | None = None, + quality_available: np.ndarray | None = None, +) -> Projection: + """Project source cells using overlap * quality * observed validity. + + Row provenance uses any valid dimension. Feature values use validity per + dimension, so partially observed physical features remain partially missing. + """ + source = np.asarray(values, dtype=np.float32) + src = np.asarray(source_intervals, dtype=np.float64) + dst = np.asarray(target_intervals, dtype=np.float64) + if source.ndim != 2 or src.shape != (len(source), 2) or dst.ndim != 2 or dst.shape[1] != 2: + raise ValueError("inconsistent source features or interval dimensions") + if not np.isfinite(source).all() or not np.isfinite(src).all() or not np.isfinite(dst).all(): + raise ValueError("non-finite source features or intervals") + if np.any(src[:, 1] < src[:, 0]) or np.any(dst[:, 1] <= dst[:, 0]): + raise ValueError("source widths must be nonnegative and target widths positive") + obs = np.asarray(observed, bool) + if obs.shape == (len(source),): + obs_dim = np.broadcast_to(obs[:, None], source.shape) + elif obs.shape == source.shape: + obs_dim = obs + obs = obs.any(axis=1) + else: + raise ValueError("observed must have source-row or source-feature shape") + q = np.ones(len(source), dtype=np.float64) if quality is None else np.asarray(quality, dtype=np.float64) + if q.shape != (len(source),) or not np.isfinite(q).all() or np.any(q < 0): + raise ValueError("quality must be finite and nonnegative per source row") + overlap = np.maximum(0.0, np.minimum(dst[:, None, 1], src[None, :, 1]) + - np.maximum(dst[:, None, 0], src[None, :, 0])) + available = np.zeros(len(source), bool) if quality_available is None else np.asarray(quality_available, bool) + if available.shape != (len(source),): + raise ValueError("quality availability must be per source row") + # Keep the same multiplication and accumulation order as the original + # official-unaligned projection when quality is uniformly one. + physical = overlap.copy() + physical *= obs[None, :] + row_weight = physical.copy() + row_weight *= q[None, :] + mass = row_weight.sum(axis=1) + row_valid = mass > 0 + normalized = np.zeros_like(row_weight, dtype=np.float32) + normalized[row_valid] = (row_weight[row_valid] / mass[row_valid, None]).astype(np.float32) + support = row_weight > 0 + count = support.sum(axis=1).astype(np.uint16) + first = np.full(len(dst), -1, dtype=np.int32) + last = np.full(len(dst), -1, dtype=np.int32) + if row_valid.any(): + first[row_valid] = support[row_valid].argmax(axis=1) + last[row_valid] = len(source) - 1 - support[row_valid, ::-1].argmax(axis=1) + width = dst[:, 1] - dst[:, 0] + physical_mass = physical.sum(axis=1) + coverage = np.clip(physical_mass / width, 0.0, 1.0).astype(np.float32) + qmean = np.ones(len(dst), np.float32) + qavailable = np.zeros(len(dst), np.float32) + physical_valid = physical_mass > 0 + qmean[physical_valid] = (mass[physical_valid] / physical_mass[physical_valid]).astype(np.float32) + qavailable[physical_valid] = ((physical[physical_valid] @ available.astype(np.float64)) + / physical_mass[physical_valid]).astype(np.float32) + + # The common full-dimension case follows the original matrix product + # exactly; this is also much faster for 500 x 768 input. + if np.array_equal(obs_dim, np.broadcast_to(obs[:, None], source.shape)): + x = np.zeros((len(dst), source.shape[1]), np.float32) + x[row_valid] = ((row_weight[row_valid] @ source) / mass[row_valid, None]).astype(np.float32) + observed_dimensions = np.broadcast_to(row_valid[:, None], x.shape).copy() + coverage_dimensions = np.broadcast_to(coverage[:, None], x.shape).copy() + else: + dim_physical = overlap[:, :, None] * obs_dim[None, :, :] + dim_weight = dim_physical * q[None, :, None] + dim_mass = dim_weight.sum(axis=1) + observed_dimensions = dim_mass > 0 + x = np.zeros((len(dst), source.shape[1]), np.float32) + numerator = np.einsum("ksd,sd->kd", dim_weight, source, optimize=True) + x[observed_dimensions] = (numerator[observed_dimensions] / dim_mass[observed_dimensions]).astype(np.float32) + coverage_dimensions = np.clip(dim_physical.sum(axis=1) / width[:, None], 0, 1).astype(np.float32) + return Projection(x, row_valid, coverage, count, first, last, + sparse.csr_matrix(normalized), observed_dimensions, coverage_dimensions, + qmean, qavailable) diff --git a/submit/final/adapter/source.py b/submit/final/adapter/source.py new file mode 100644 index 0000000..9e27ac9 --- /dev/null +++ b/submit/final/adapter/source.py @@ -0,0 +1,51 @@ +"""Read the native Q1 artifacts needed by the unified alignment adapter.""" +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +import numpy as np + +FEATURE_DIR = Path(__file__).resolve().parents[1] / "output" / "q1" / "features_v2" + + +def load_sample(sample_id: str, feature_dir: Path = FEATURE_DIR) -> dict[str, Any]: + """Load a Q1 sample by its video/clip ID from a feature directory.""" + feature_dir = Path(feature_dir) + manifest = feature_dir / "manifest_q1.jsonl" + for line in manifest.read_text(encoding="utf-8").splitlines(): + row = json.loads(line) + if row["sample_id"] != sample_id: + continue + path = feature_dir / Path(row["feature_path"]).name + with np.load(path, allow_pickle=False) as archive: + result = {name: archive[name] for name in archive.files} + result["_path"] = path + result["_manifest"] = row + result["_meta"] = json.loads(str(result["meta_json"])) + return result + raise KeyError(f"sample_id not found: {sample_id}") + + +def native_arrays(sample: dict[str, Any], modality: str): + """Return values, observed dimensions, time intervals and quality evidence.""" + if modality == "text": + values = sample["native_text_features"].astype(np.float32) + return ( + values, + np.broadcast_to(sample["native_text_observed"][:, None], values.shape), + sample["native_text_intervals"].astype(np.float32), + sample["native_text_quality_effective"].astype(np.float32), + sample["native_text_quality_available"].astype(bool), + ) + if modality in ("audio", "vision"): + prefix = f"native_{modality}_" + return ( + sample[prefix + "features"].astype(np.float32), + sample[prefix + "mask"].astype(bool), + sample[prefix + "intervals"].astype(np.float32), + sample[prefix + "quality"].astype(np.float32), + sample[prefix + "quality_available"].astype(bool), + ) + raise ValueError(f"unknown modality: {modality}") diff --git a/submit/final/data_paths.py b/submit/final/data_paths.py new file mode 100644 index 0000000..7abbd70 --- /dev/null +++ b/submit/final/data_paths.py @@ -0,0 +1,13 @@ +"""Shared input-data locations for the standalone deliverable.""" +from __future__ import annotations + +import os +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parent +DATA_ROOT = Path(os.environ.get("FINAL_DATA_DIR", PROJECT_ROOT / "data")).expanduser().resolve() + +ATTACHMENT1 = DATA_ROOT / "附件1-数据集原始多模态样本" / "MOSEI数据集部分原始视频-100条" +ATTACHMENT2 = DATA_ROOT / "附件2-数据集特征文件" +ATTACHMENT3 = DATA_ROOT / "附件3-模态缺失特征样本" +ATTACHMENT4 = DATA_ROOT / "附件4-可解释专项视频样本与特征文件" diff --git a/submit/final/experiments/q2/unaligned_deep_two_b128/unaligned_50_robust_stats.npz b/submit/final/experiments/q2/unaligned_deep_two_b128/unaligned_50_robust_stats.npz new file mode 100644 index 0000000..5ba5556 Binary files /dev/null and b/submit/final/experiments/q2/unaligned_deep_two_b128/unaligned_50_robust_stats.npz differ diff --git a/submit/final/experiments/q2/unaligned_math_all_b128/crg_student.pt b/submit/final/experiments/q2/unaligned_math_all_b128/crg_student.pt new file mode 100644 index 0000000..4faa4b2 Binary files /dev/null and b/submit/final/experiments/q2/unaligned_math_all_b128/crg_student.pt differ diff --git a/submit/final/experiments/q2/unaligned_math_all_b128/preprocessor.npz b/submit/final/experiments/q2/unaligned_math_all_b128/preprocessor.npz new file mode 100644 index 0000000..7fd83ae Binary files /dev/null and b/submit/final/experiments/q2/unaligned_math_all_b128/preprocessor.npz differ diff --git a/submit/final/experiments/q2/unaligned_math_all_b128/run_manifest.json b/submit/final/experiments/q2/unaligned_math_all_b128/run_manifest.json new file mode 100644 index 0000000..95d7b1f --- /dev/null +++ b/submit/final/experiments/q2/unaligned_math_all_b128/run_manifest.json @@ -0,0 +1,422 @@ +{ + "seed": 20260924, + "text_encoder": "official precomputed text field; encoder revision not supplied", + "training_configuration": { + "student_epoch_limit": 12, + "imputer_epochs": 8, + "batch_size": 128, + "early_stopping_patience": 3, + "device": "cuda", + "device_name": "NVIDIA GeForce RTX 5070 Ti", + "optimizer": "AdamW", + "student_learning_rate": 0.0003, + "student_weight_decay": 0.001, + "imputer_learning_rate": 0.0003, + "imputer_weight_decay": 0.0001, + "early_stopping_metric": "mean untempered selection_nll over fixed group-disjoint internal training scenarios", + "inner_selection_scenarios": [ + "0.0/natural", + "0.3/single", + "0.3/sync", + "0.5/async" + ], + "inner_selection_source_video_groups": 76 + }, + "training_input": "E题数据/附件2-数据集特征文件/unaligned_50.pkl", + "input_version": "unaligned_50", + "q1_alignment_adapter": { + "train": { + "method": "shared_interval_overlap_on_normalized_progress", + "coordinate_mode": "relative", + "physical_time_alignment": false, + "samples": 3395, + "vision_length_conflict_samples": 618, + "vision_tail_ambiguous_samples": 618, + "nonzero_text_rows_outside_attention": 86078, + "observed_target_rows": { + "text": 169750, + "audio": 169750, + "vision": 163302 + }, + "mean_target_coverage": { + "text": 1.0, + "audio": 1.0, + "vision": 0.9540337701314328 + }, + "quality_fields_available": false, + "word_or_frame_timestamps_available": false + }, + "valid": { + "method": "shared_interval_overlap_on_normalized_progress", + "coordinate_mode": "relative", + "physical_time_alignment": false, + "samples": 728, + "vision_length_conflict_samples": 141, + "vision_tail_ambiguous_samples": 141, + "nonzero_text_rows_outside_attention": 17772, + "observed_target_rows": { + "text": 36400, + "audio": 36400, + "vision": 35315 + }, + "mean_target_coverage": { + "text": 1.0, + "audio": 1.0, + "vision": 0.96090314748523 + }, + "quality_fields_available": false, + "word_or_frame_timestamps_available": false + }, + "test": { + "method": "shared_interval_overlap_on_normalized_progress", + "coordinate_mode": "relative", + "physical_time_alignment": false, + "samples": 727, + "vision_length_conflict_samples": 131, + "vision_tail_ambiguous_samples": 131, + "nonzero_text_rows_outside_attention": 18041, + "observed_target_rows": { + "text": 36350, + "audio": 36350, + "vision": 35034 + }, + "mean_target_coverage": { + "text": 1.0, + "audio": 1.0, + "vision": 0.9549938172651288 + }, + "quality_fields_available": false, + "word_or_frame_timestamps_available": false + } + }, + "training_sha256": "77eda14a06be9749a96c52ae45470c7cffcfa7219011eae391d231a0664c3762", + "official_group_overlap": { + "train_valid": 0, + "train_test": 0, + "valid_test": 0 + }, + "official_splits": { + "train": { + "n": 3395, + "source_video_groups": 1528 + }, + "valid": { + "n": 728, + "source_video_groups": 239 + }, + "test": { + "n": 727, + "source_video_groups": 381 + } + }, + "internal_train_holdouts": { + "fit": { + "n": 3030, + "video_groups": 1375 + }, + "reliability_selection": { + "n": 190, + "video_groups": 76 + }, + "temperature_calibration": { + "n": 175, + "video_groups": 77 + }, + "all_group_disjoint": true + }, + "feature_standardization": "fit-only observed rows, per-dimension; fixed for valid/test/attachment3", + "missing_mask": "official text attention and source lengths plus row observation; normalized-progress overlap preserves empty bins; q*=1 only where visible, J_Q=0", + "observation_quality": { + "quality_score_fields_present": false, + "quality_available_flag_present": false, + "fallback": "q*=1 and J_Q=0 for visible rows; R_eff=R", + "quality_noise_mapping_ablation": "not identifiable on unaligned_50 because no row quality score varies" + }, + "imputer": { + "type": "structured linear Gaussian shared-private state space", + "state_dims": { + "shared": 8, + "private_each": 4 + }, + "posterior": "block-tridiagonal equivalent Kalman information filter + RTS smoother", + "sampling": "joint latent trajectories and missing emissions; observed features copied exactly", + "fit_objective": "train-only observed Gaussian marginal likelihood including log determinants", + "epochs": 8, + "frozen_before_teacher_student": true + }, + "architecture": { + "projection": 32, + "bigru_hidden_each_direction": 16, + "cross_source_layers": 1, + "cross_time_read": true, + "rank": 4, + "reliability_gru": "directional hidden decay; reset applied before candidate map; update gate multiplied by rho", + "final_gate": "rho times bounded content score plus positive null prior", + "output": "neutral point mass plus sign-specific Beta magnitudes; K-path probabilities mixed before decoding" + }, + "reliability_hyperparameters": { + "selected_per_model_on": "group-disjoint internal training reliability-validation slice", + "candidate_values": [ + [ + 0.5, + 0.0, + 0.0, + 0.0 + ], + [ + 0.5, + 0.05, + 0.05, + 0.05 + ], + [ + 0.5, + 0.1, + 0.0, + 0.0 + ], + [ + 0.3, + 0.05, + 0.05, + 0.05 + ], + [ + 0.7, + 0.05, + 0.05, + 0.05 + ] + ], + "validation_scenarios": [ + "0.0/natural", + "0.3/single", + "0.3/sync", + "0.5/async" + ], + "selected_by_model": { + "teacher": [ + 0.3, + 0.05, + 0.05, + 0.05 + ], + "C1": [ + 0.5, + 0.05, + 0.05, + 0.05 + ], + "C2": [ + 0.5, + 0.05, + 0.05, + 0.05 + ], + "C3": [ + 0.5, + 0.0, + 0.0, + 0.0 + ], + "C4": [ + 0.7, + 0.05, + 0.05, + 0.05 + ], + "C5": [ + 0.3, + 0.05, + 0.05, + 0.05 + ], + "C6": [ + 0.3, + 0.05, + 0.05, + 0.05 + ], + "C6_no_distance": [ + 0.5, + 0.0, + 0.0, + 0.0 + ], + "C6_no_reconstruction": [ + 0.3, + 0.05, + 0.05, + 0.05 + ], + "C6_pointmask": [ + 0.3, + 0.05, + 0.05, + 0.05 + ], + "C7_distill": [ + 0.5, + 0.05, + 0.05, + 0.05 + ], + "C7_group": [ + 0.5, + 0.0, + 0.0, + 0.0 + ] + } + }, + "group_risk_hyperparameters": { + "selection": "lambda_group and group_temperature jointly selected with reliability hyperparameters on fixed group-disjoint internal training scenarios", + "candidate_values": [ + [ + 0.05, + 0.1 + ], + [ + 0.1, + 0.05 + ], + [ + 0.1, + 0.1 + ], + [ + 0.1, + 0.2 + ], + [ + 0.2, + 0.1 + ] + ], + "selected": [ + 0.1, + 0.1 + ], + "selection_split": "reliability_validation" + }, + "loss": { + "supervision": "negative log mixture of Beta interval masses plus scaled Huber mean term", + "delta_u": 0.027777499999999997, + "delta_u_source": "half the minimum positive spacing of nonzero absolute labels in fit only", + "lambda_y": 1.0, + "lambda_distill": 0.1, + "lambda_reconstruction": 0.05, + "lambda_group_default": 0.1, + "group_temperature_default": 0.1, + "selected_group_risk": [ + 0.1, + 0.1 + ], + "distill_temperature": 2.0, + "distill_retention_exponent": 1.0, + "imputer_regularization": { + "emission_l2": 0.0001, + "transition_l2": 0.0001 + }, + "group_and_distill_separate": true + }, + "calibration": { + "method": "temperature scaling on a group-disjoint internal official-train holdout, separated from reliability selection", + "temperature": 1.122980387832455, + "valid_used_for_selection": true, + "test_used_for_selection_or_calibration": false + }, + "selected_model": "C6", + "attachment3_low_information_priors": { + "class_probability_method": "fit counts + one pseudocount per class", + "class_probability_values": [ + 0.2786020441806792, + 0.2205736894164194, + 0.5008242664029015 + ], + "negative_beta": [ + 1.1441766023635864, + 1.8200817108154297 + ], + "positive_beta": [ + 1.271026611328125, + 2.4681642055511475 + ] + }, + "ablation_definitions": { + "C1": "masked BiGRU; no posterior imputation, explicit reliability or source gate", + "C2": "exact Gaussian posterior mean; no joint trajectory integral", + "C3": "joint trajectory integral plus final reliability/content fusion gate", + "C4": "C3 plus bounded cross-time source attention and null source", + "C5": "C4 plus reliability-modulated BiGRU update", + "C6": "C5 plus optional rank-4 CP residual", + "C6_no_distance": "C6 with uncertainty retained but both distance/span reliability penalties fixed to zero", + "C6_no_reconstruction": "C6 trained without the auxiliary hidden-feature reconstruction loss", + "C6_pointmask": "C6 trained with independent point masking instead of contiguous spans", + "C7_distill": "C6 plus entropy/retention-weighted teacher distillation only", + "C7_group": "C6 plus smooth worst-group risk only" + }, + "masking": { + "rates": [ + 0.0, + 0.1, + 0.3, + 0.5, + 0.7 + ], + "patterns": [ + "single", + "sync", + "partial", + "async" + ], + "preserve_at_least_fraction_per_selected_modality": 0.2, + "controlled_sweep_split": "official validation", + "identical_masks_across_models": true, + "scenario_count": 42, + "scenario_seed": 20261833, + "training_mask_rng_seed": 20261227, + "reliability_scenario_seed": 20261830, + "controlled_torch_sampling_seed": 20261476, + "paired_control_bootstrap_seed": 20261477, + "mask_audit_file": null, + "mask_audit_omitted_reason": "Row-level audit omitted from the size-limited deliverable; regenerated by rerunning final.q2.math.train.", + "additional_one_factor_controls": [ + "modality T/A/V and combinations", + "start/middle/end", + "one-long/multiple-short", + "sync/partial/async" + ], + "semantic_position_control": "not run: unaligned_50 does not provide audited semantic boundary indices; raw text is prohibited in student inputs" + }, + "final_test_metrics": { + "n": 727, + "accuracy": 0.672627235213205, + "macro_f1": 0.5484581573154621, + "negative_support": 207, + "neutral_support": 158, + "positive_support": 362, + "negative_recall": 0.7439613526570048, + "middle_recall": 0.10126582278481013, + "positive_recall": 0.8812154696132597, + "regression_mae": 0.7100059986114502, + "regression_rmse": 0.969292458045841, + "pearson": 0.6424147486686707, + "brier": 0.4367243729993746, + "classification_nll": 0.7538501024246216, + "ece_15": 0.047142177615237854, + "selection_nll": 2.8449835777282715, + "interval_90_coverage": 0.8954607977991746, + "interval_90_mean_width": 2.48697829246521, + "predictive_variance_mean_uncalibrated": 0.6001424193382263, + "within_trajectory_variance_mean": 0.6001414060592651, + "between_trajectory_variance_mean": 1.021712705551181e-06, + "predictive_mean_mean_calibrated": 0.18709982931613922, + "predictive_variance_mean_calibrated": 0.6344733238220215 + }, + "test_gate_diagnostics_file": null, + "test_gate_diagnostics_omitted_reason": "Row-level audit omitted from the size-limited deliverable; regenerated by rerunning final.q2.math.train.", + "attachment3_cases": 0, + "attachment3_labeled_metrics": null, + "completed_utc": "2026-09-25T12:10:16Z" +} diff --git a/submit/final/experiments/q2/unaligned_math_all_b128/structured_imputer.pt b/submit/final/experiments/q2/unaligned_math_all_b128/structured_imputer.pt new file mode 100644 index 0000000..062e5e6 Binary files /dev/null and b/submit/final/experiments/q2/unaligned_math_all_b128/structured_imputer.pt differ diff --git a/submit/final/experiments/q2/unaligned_math_all_b128/validation_metrics.json b/submit/final/experiments/q2/unaligned_math_all_b128/validation_metrics.json new file mode 100644 index 0000000..8cec5eb --- /dev/null +++ b/submit/final/experiments/q2/unaligned_math_all_b128/validation_metrics.json @@ -0,0 +1,27 @@ +{ + "n": 728, + "accuracy": 0.6085164835164835, + "macro_f1": 0.518781225964892, + "negative_support": 206, + "neutral_support": 184, + "positive_support": 338, + "negative_recall": 0.6796116504854369, + "middle_recall": 0.125, + "positive_recall": 0.8284023668639053, + "regression_mae": 0.6846789717674255, + "regression_rmse": 0.9208215740080159, + "pearson": 0.6101368069648743, + "brier": 0.4977383080922297, + "classification_nll": 0.8427478075027466, + "ece_15": 0.03950894476620705, + "selection_nll": 2.783693552017212, + "interval_90_coverage": 0.8873626373626373, + "interval_90_mean_width": 2.3910491466522217, + "predictive_variance_mean_uncalibrated": 0.5537729859352112, + "within_trajectory_variance_mean": 0.5537727475166321, + "between_trajectory_variance_mean": 2.9140662149984564e-07, + "predictive_mean_mean_calibrated": 0.2094990462064743, + "predictive_variance_mean_calibrated": 0.5816943049430847, + "selected_model": "C6", + "temperature": 1.122980387832455 +} \ No newline at end of file diff --git a/submit/final/experiments/q3/ati_ho/final_selection.json b/submit/final/experiments/q3/ati_ho/final_selection.json new file mode 100644 index 0000000..fc7aa74 --- /dev/null +++ b/submit/final/experiments/q3/ati_ho/final_selection.json @@ -0,0 +1,37 @@ +{ + "selected_method": "A0", + "provisional_seed42_method": "A2", + "candidate_methods_with_three_seeds": [ + "A2", + "A0", + "A1" + ], + "selection_rule": "lowest mean fixed four-scenario validation task loss across seeds 42, 3407, 2026", + "candidate_summary": [ + { + "method": "A0", + "seed_losses": "[0.8643234267339601, 0.8756466648735842, 0.8574190991265433]", + "mean_validation_selection_loss": 0.8657963969113626, + "std_validation_selection_loss": 0.009202622948013908, + "seeds": 3, + "validation_only_selection": true + }, + { + "method": "A1", + "seed_losses": "[0.8649765662439577, 0.8810990981675766, 0.8557776766163963]", + "mean_validation_selection_loss": 0.8672844470093102, + "std_validation_selection_loss": 0.012817501026466038, + "seeds": 3, + "validation_only_selection": true + }, + { + "method": "A2", + "seed_losses": "[0.8634063961741689, 0.880271397449158, 0.862087192279952]", + "mean_validation_selection_loss": 0.8685883286344263, + "std_validation_selection_loss": 0.010139311979910028, + "seeds": 3, + "validation_only_selection": true + } + ], + "attachment4_labels_used": false +} diff --git a/submit/final/experiments/q3/ati_ho/models/A0/seed_2026/model_best.pt b/submit/final/experiments/q3/ati_ho/models/A0/seed_2026/model_best.pt new file mode 100644 index 0000000..a7953bb Binary files /dev/null and b/submit/final/experiments/q3/ati_ho/models/A0/seed_2026/model_best.pt differ diff --git a/submit/final/experiments/q3/ati_ho/models/A0/seed_2026/training_manifest.json b/submit/final/experiments/q3/ati_ho/models/A0/seed_2026/training_manifest.json new file mode 100644 index 0000000..555d849 --- /dev/null +++ b/submit/final/experiments/q3/ati_ho/models/A0/seed_2026/training_manifest.json @@ -0,0 +1,42 @@ +{ + "method": "A0", + "seed": 2026, + "best_epoch": 4, + "best_selection_loss": 0.8574190991265433, + "batch_size": 64, + "epoch_limit": 12, + "patience": 3, + "optimizer": "AdamW", + "learning_rate": 0.0003, + "weight_decay": 0.001, + "gradient_clip_norm": 1.0, + "training_mask_rates": [ + 0.0, + 0.1, + 0.3, + 0.5, + 0.7 + ], + "training_mask_patterns": [ + "single", + "sync", + "partial", + "async" + ], + "training_mask_seed_base": 20261227, + "same_orders_and_masks_across_methods_for_same_seed": true, + "config": { + "name": "A0_main_effects", + "low_rank": false, + "cross_attention": false, + "anchored": true, + "lambda_interaction": 0.001, + "lambda_mask": 0.0, + "rank": 4, + "hidden": 64, + "gru_hidden_per_direction": 32, + "attention_heads": 4, + "attention_ffn": 128, + "eta_init": 0.1 + } +} diff --git a/submit/final/experiments/q3/ati_ho/models/A0/seed_3407/model_best.pt b/submit/final/experiments/q3/ati_ho/models/A0/seed_3407/model_best.pt new file mode 100644 index 0000000..eaaf75d Binary files /dev/null and b/submit/final/experiments/q3/ati_ho/models/A0/seed_3407/model_best.pt differ diff --git a/submit/final/experiments/q3/ati_ho/models/A0/seed_3407/training_manifest.json b/submit/final/experiments/q3/ati_ho/models/A0/seed_3407/training_manifest.json new file mode 100644 index 0000000..5696227 --- /dev/null +++ b/submit/final/experiments/q3/ati_ho/models/A0/seed_3407/training_manifest.json @@ -0,0 +1,42 @@ +{ + "method": "A0", + "seed": 3407, + "best_epoch": 3, + "best_selection_loss": 0.8756466648735842, + "batch_size": 64, + "epoch_limit": 12, + "patience": 3, + "optimizer": "AdamW", + "learning_rate": 0.0003, + "weight_decay": 0.001, + "gradient_clip_norm": 1.0, + "training_mask_rates": [ + 0.0, + 0.1, + 0.3, + 0.5, + 0.7 + ], + "training_mask_patterns": [ + "single", + "sync", + "partial", + "async" + ], + "training_mask_seed_base": 20261227, + "same_orders_and_masks_across_methods_for_same_seed": true, + "config": { + "name": "A0_main_effects", + "low_rank": false, + "cross_attention": false, + "anchored": true, + "lambda_interaction": 0.001, + "lambda_mask": 0.0, + "rank": 4, + "hidden": 64, + "gru_hidden_per_direction": 32, + "attention_heads": 4, + "attention_ffn": 128, + "eta_init": 0.1 + } +} diff --git a/submit/final/experiments/q3/ati_ho/models/A0/seed_42/model_best.pt b/submit/final/experiments/q3/ati_ho/models/A0/seed_42/model_best.pt new file mode 100644 index 0000000..cde9ddf Binary files /dev/null and b/submit/final/experiments/q3/ati_ho/models/A0/seed_42/model_best.pt differ diff --git a/submit/final/experiments/q3/ati_ho/models/A0/seed_42/training_manifest.json b/submit/final/experiments/q3/ati_ho/models/A0/seed_42/training_manifest.json new file mode 100644 index 0000000..446314d --- /dev/null +++ b/submit/final/experiments/q3/ati_ho/models/A0/seed_42/training_manifest.json @@ -0,0 +1,42 @@ +{ + "method": "A0", + "seed": 42, + "best_epoch": 4, + "best_selection_loss": 0.8643234267339601, + "batch_size": 64, + "epoch_limit": 12, + "patience": 3, + "optimizer": "AdamW", + "learning_rate": 0.0003, + "weight_decay": 0.001, + "gradient_clip_norm": 1.0, + "training_mask_rates": [ + 0.0, + 0.1, + 0.3, + 0.5, + 0.7 + ], + "training_mask_patterns": [ + "single", + "sync", + "partial", + "async" + ], + "training_mask_seed_base": 20261227, + "same_orders_and_masks_across_methods_for_same_seed": true, + "config": { + "name": "A0_main_effects", + "low_rank": false, + "cross_attention": false, + "anchored": true, + "lambda_interaction": 0.001, + "lambda_mask": 0.0, + "rank": 4, + "hidden": 64, + "gru_hidden_per_direction": 32, + "attention_heads": 4, + "attention_ffn": 128, + "eta_init": 0.1 + } +} diff --git a/submit/final/experiments/q3/ati_ho/run_manifest.json b/submit/final/experiments/q3/ati_ho/run_manifest.json new file mode 100644 index 0000000..1e2030b --- /dev/null +++ b/submit/final/experiments/q3/ati_ho/run_manifest.json @@ -0,0 +1,119 @@ +{ + "experiment": "ATI\u2013HO Q3 staged training and structural attribution audit", + "created_utc": "2026-09-26T07:33:41Z", + "device": "cuda", + "torch_version": "2.14.0+cu130", + "cuda_available": true, + "cuda_version": "13.0", + "gpu": "NVIDIA GeForce RTX 5070 Ti", + "python": "3.14.7", + "seeds": [ + 42, + 3407, + 2026 + ], + "epochs_max": 12, + "training_protocol": { + "batch_size": 64, + "early_stopping_patience": 3, + "optimizer": "AdamW", + "learning_rate": 0.0003, + "weight_decay": 0.001, + "gradient_clip_norm": 1.0, + "training_mask_rates": [ + 0.0, + 0.1, + 0.3, + 0.5, + 0.7 + ], + "training_mask_patterns": [ + "single", + "sync", + "partial", + "async" + ], + "validation_selection_scenarios": [ + "0.0/none", + "0.3/single", + "0.3/sync", + "0.5/async" + ], + "held_out_attachment4_touched_during_training": false + }, + "ati_output": { + "parameter_vector": "3 centered class logits + r_negative + r_positive", + "intensity": "negative/positive magnitudes are 3*sigmoid(r); neutral class is exactly zero", + "loss": "cross entropy + conditional magnitude SmoothL1 + 0.2*Huber(delta=0.25) + configured regularizers", + "baseline_checkpoint_reuse": "No: retrain B0 and B1 on the fixed ATI split/mask schedule because existing Q2 checkpoints differ in seeds, batch size, and schedule.", + "calibration_temperature": 1.0 + }, + "data": { + "feature_file": "${FINAL_DATA_DIR}/attachment2/unaligned_50.pkl", + "feature_sha256": "77eda14a06be9749a96c52ae45470c7cffcfa7219011eae391d231a0664c3762", + "scaler_file": "final/experiments/q2/unaligned_deep_two_b128/unaligned_50_robust_stats.npz", + "scaler_max_abs_difference_from_train_only_recompute": 0.0, + "representation": "Q1 adapter Relative-Progress projection; 50 slots; not physical-time alignment", + "adapter": "final.adapter.adapt_official_split; shared train-only robust scaler retained from Q2 V2", + "dimensions": [ + 768, + 74, + 35 + ], + "train_samples": 3395, + "valid_samples": 728, + "train_source_video_groups": 1528, + "valid_source_video_groups": 239, + "test_samples": 727, + "test_source_video_groups": 381, + "source_video_overlap_counts": { + "train/valid": 0, + "train/test": 0, + "valid/test": 0 + }, + "adapter_audit": { + "train": { + "method": "shared_interval_overlap_on_normalized_progress", + "coordinate_mode": "relative", + "physical_time_alignment": false, + "samples": 3395, + "vision_length_conflict_samples": 618, + "vision_tail_ambiguous_samples": 618, + "nonzero_text_rows_outside_attention": 86078, + "observed_target_rows": { + "text": 169750, + "audio": 169750, + "vision": 163302 + }, + "mean_target_coverage": { + "text": 1.0, + "audio": 1.0, + "vision": 0.9540337701314328 + }, + "quality_fields_available": false, + "word_or_frame_timestamps_available": false + }, + "valid": { + "method": "shared_interval_overlap_on_normalized_progress", + "coordinate_mode": "relative", + "physical_time_alignment": false, + "samples": 728, + "vision_length_conflict_samples": 141, + "vision_tail_ambiguous_samples": 141, + "nonzero_text_rows_outside_attention": 17772, + "observed_target_rows": { + "text": 36400, + "audio": 36400, + "vision": 35315 + }, + "mean_target_coverage": { + "text": 1.0, + "audio": 1.0, + "vision": 0.96090314748523 + }, + "quality_fields_available": false, + "word_or_frame_timestamps_available": false + } + } + } +} diff --git a/submit/final/model/__init__.py b/submit/final/model/__init__.py new file mode 100644 index 0000000..65bb1a4 --- /dev/null +++ b/submit/final/model/__init__.py @@ -0,0 +1,23 @@ +"""Q2 model entry points, one file per paper scheme.""" + +from .crg import CRG, StructuredGaussianImputer +from .c0 import C0 +from .c1 import C1 +from .c2 import C2 +from .c3 import C3 +from .c4 import C4 +from .c5 import C5 +from .c6 import C6 +from .c6_no_distance import C6NoDistance +from .c6_no_reconstruction import C6NoReconstruction +from .c6_pointmask import C6PointMask +from .c7_distill import C7Distill +from .c7_group import C7Group +from .early_concat import AlignedFusionModel +from .mofe import MixtureOfFusionExperts + +__all__ = [ + "CRG", "StructuredGaussianImputer", "C0", "C1", "C2", "C3", "C4", "C5", "C6", + "C6NoDistance", "C6NoReconstruction", "C6PointMask", "C7Distill", "C7Group", + "AlignedFusionModel", "MixtureOfFusionExperts", +] diff --git a/submit/final/model/ati_ho.py b/submit/final/model/ati_ho.py new file mode 100644 index 0000000..e9d4326 --- /dev/null +++ b/submit/final/model/ati_ho.py @@ -0,0 +1,358 @@ +from __future__ import annotations + +import math +from typing import Any + +import torch +import torch.nn.functional as F +from torch import nn + +from .ati_ho_config import ATIConfig + + +PAIR_INDICES = ((0, 1), (0, 2), (1, 2)) +PAIR_NAMES = ("TA", "TV", "AV") + + +def _center_class_parameters(value: torch.Tensor) -> torch.Tensor: + """Apply C to the three class logits while leaving magnitude parameters alone.""" + logits = value[..., :3] + logits = logits - logits.mean(dim=-1, keepdim=True) + return torch.cat((logits, value[..., 3:]), dim=-1) + + +class PrivateTemporalEncoder(nn.Module): + """One modality-private projection, BiGRU(32 each way), and attention pool.""" + + def __init__(self, input_dim: int, hidden: int, gru_hidden: int) -> None: + super().__init__() + self.projection = nn.Sequential( + nn.Linear(input_dim, hidden), nn.GELU(), nn.LayerNorm(hidden) + ) + self.temporal = nn.GRU( + input_size=hidden, + hidden_size=gru_hidden, + num_layers=1, + batch_first=True, + bidirectional=True, + ) + self.pool_score = nn.Linear(hidden, 1) + self.output_dim = 2 * gru_hidden + + def forward(self, x: torch.Tensor, observed: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: + observed = observed.bool() + projected = self.projection(x) + projected = projected * observed.unsqueeze(-1).to(projected.dtype) + sequence, _ = self.temporal(projected) + sequence = sequence * observed.unsqueeze(-1).to(sequence.dtype) + scores = self.pool_score(torch.tanh(sequence)).squeeze(-1) + scores = scores.masked_fill(~observed, torch.finfo(scores.dtype).min) + has_any = observed.any(dim=1, keepdim=True) + weights = torch.softmax(scores, dim=1) + weights = torch.where(has_any, weights, torch.zeros_like(weights)) + pooled = torch.sum(sequence * weights.unsqueeze(-1), dim=1) + return sequence, pooled + + +class MainEffectHead(nn.Module): + def __init__(self, hidden: int) -> None: + super().__init__() + self.network = nn.Sequential(nn.Linear(hidden, hidden), nn.GELU(), nn.Linear(hidden, 5)) + + def forward(self, pooled: torch.Tensor) -> torch.Tensor: + return _center_class_parameters(self.network(pooled)) + + +class AnchoredPairBranch(nn.Module): + """A pair reads only two private streams; its four-term anchor is explicit.""" + + def __init__(self, hidden: int, config: ATIConfig) -> None: + super().__init__() + self.low_rank_enabled = config.low_rank + self.cross_attention_enabled = config.cross_attention + self.rank = config.rank + if self.low_rank_enabled: + self.left_factor = nn.Linear(hidden, config.rank) + self.right_factor = nn.Linear(hidden, config.rank) + self.low_rank_out = nn.Linear(config.rank, 5, bias=False) + else: + self.left_factor = None + self.right_factor = None + self.low_rank_out = None + + if self.cross_attention_enabled: + self.left_to_right = nn.MultiheadAttention( + hidden, config.attention_heads, batch_first=True + ) + self.right_to_left = nn.MultiheadAttention( + hidden, config.attention_heads, batch_first=True + ) + self.left_norm1 = nn.LayerNorm(hidden) + self.right_norm1 = nn.LayerNorm(hidden) + self.left_ffn = nn.Sequential( + nn.Linear(hidden, config.attention_ffn), + nn.GELU(), + nn.Linear(config.attention_ffn, hidden), + ) + self.right_ffn = nn.Sequential( + nn.Linear(hidden, config.attention_ffn), + nn.GELU(), + nn.Linear(config.attention_ffn, hidden), + ) + self.left_norm2 = nn.LayerNorm(hidden) + self.right_norm2 = nn.LayerNorm(hidden) + self.cross_out = nn.Linear(hidden * 2, 5, bias=False) + else: + self.left_to_right = None + self.right_to_left = None + self.left_norm1 = None + self.right_norm1 = None + self.left_ffn = None + self.right_ffn = None + self.left_norm2 = None + self.right_norm2 = None + self.cross_out = None + + init = min(max(config.eta_init, 1e-5), 1 - 1e-5) + self.eta_logit = nn.Parameter(torch.tensor(math.log(init / (1.0 - init)))) + # q(x0,y)=q(x,y0)=q(x0,y0)=offset. Four-term subtraction cancels it. + # D0 deliberately leaves this offset in the output as a leakage control. + self.anchor_offset = nn.Parameter(torch.zeros(5)) + + @staticmethod + def _masked_mean(sequence: torch.Tensor, mask: torch.Tensor) -> torch.Tensor: + weights = mask.to(sequence.dtype).unsqueeze(-1) + return (sequence * weights).sum(dim=1) / weights.sum(dim=1).clamp_min(1.0) + + @staticmethod + def _safe_key_mask(mask: torch.Tensor) -> torch.Tensor: + safe = mask.clone() + empty = ~safe.any(dim=1) + if empty.any(): + safe[empty, 0] = True + return safe + + def _core( + self, + left: torch.Tensor, + right: torch.Tensor, + left_mask: torch.Tensor, + right_mask: torch.Tensor, + ) -> torch.Tensor: + joint = left_mask.bool() & right_mask.bool() + values: list[torch.Tensor] = [] + if self.low_rank_enabled: + assert self.left_factor is not None and self.right_factor is not None + assert self.low_rank_out is not None + product = torch.tanh(self.left_factor(left)) * torch.tanh(self.right_factor(right)) + values.append(self.low_rank_out(self._masked_mean(product, joint))) + if self.cross_attention_enabled: + assert self.left_to_right is not None and self.right_to_left is not None + assert self.left_norm1 is not None and self.right_norm1 is not None + assert self.left_ffn is not None and self.right_ffn is not None + assert self.left_norm2 is not None and self.right_norm2 is not None + assert self.cross_out is not None + safe_left = self._safe_key_mask(left_mask.bool()) + safe_right = self._safe_key_mask(right_mask.bool()) + left_msg, _ = self.left_to_right( + left, right, right, key_padding_mask=~safe_right, need_weights=False + ) + right_msg, _ = self.right_to_left( + right, left, left, key_padding_mask=~safe_left, need_weights=False + ) + left_context = self.left_norm1(left + left_msg) + right_context = self.right_norm1(right + right_msg) + left_context = self.left_norm2(left_context + self.left_ffn(left_context)) + right_context = self.right_norm2(right_context + self.right_ffn(right_context)) + left_context = left_context * left_mask.unsqueeze(-1).to(left_context.dtype) + right_context = right_context * right_mask.unsqueeze(-1).to(right_context.dtype) + pooled = torch.cat( + (self._masked_mean(left_context, joint), self._masked_mean(right_context, joint)), + dim=-1, + ) + cross = self.cross_out(pooled) + values.append(torch.sigmoid(self.eta_logit) * cross) + if not values: + return left.new_zeros((left.shape[0], 5)) + # Each branch has a bias-free output and a joint-observation gate. Thus + # core(x, y0)=core(x0, y)=core(x0, y0)=0 exactly. + return torch.stack(values, dim=0).sum(dim=0) + + def forward( + self, + left: torch.Tensor, + right: torch.Tensor, + left_mask: torch.Tensor, + right_mask: torch.Tensor, + *, + anchored: bool, + ) -> torch.Tensor: + raw_xy = self._core(left, right, left_mask, right_mask) + self.anchor_offset + if anchored: + # Four-term difference: + # q(x,y)-q(x,x0)-q(x0,y)+q(x0,y0) = core(x,y). + # The three absent-modality terms equal anchor_offset by the + # joint gate and bias-free core, so they cancel algebraically. + value = raw_xy - self.anchor_offset + else: + value = raw_xy + return _center_class_parameters(value) + + +class ATIHOModel(nn.Module): + """Five-parameter additive multimodal predictor with exact modality anchors.""" + + def __init__(self, dims: tuple[int, int, int], config: ATIConfig, steps: int = 50) -> None: + super().__init__() + self.dims = tuple(int(d) for d in dims) + self.steps = int(steps) + self.config = config + hidden = config.hidden + self.encoders = nn.ModuleList( + PrivateTemporalEncoder(dim, hidden, config.gru_hidden_per_direction) for dim in dims + ) + self.main_heads = nn.ModuleList(MainEffectHead(hidden) for _ in dims) + self.mask_heads = nn.ModuleList(nn.Linear(hidden, 1) for _ in dims) + self.pair_branches = nn.ModuleList( + AnchoredPairBranch(hidden, config) for _ in PAIR_INDICES + ) + self.baseline = nn.Parameter(torch.zeros(5)) + + def forward( + self, + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + masks: torch.Tensor, + *, + return_details: bool = True, + ) -> dict[str, Any]: + if len(xs) != 3: + raise ValueError("ATI–HO requires text, audio, and vision streams") + if masks.ndim != 3 or masks.shape[-1] != 3: + raise ValueError(f"masks must be B x T x 3, got {tuple(masks.shape)}") + if masks.shape[1] > self.steps: + raise ValueError(f"ATI–HO supports at most {self.steps} steps") + masks = masks.bool() + + sequences: list[torch.Tensor] = [] + pooled: list[torch.Tensor] = [] + mask_logits: list[torch.Tensor] = [] + main_effects: list[torch.Tensor] = [] + for modality, (encoder, head, mask_head, x) in enumerate( + zip(self.encoders, self.main_heads, self.mask_heads, xs) + ): + if x.shape[-1] != self.dims[modality]: + raise ValueError( + f"modality {modality} has {x.shape[-1]} features, expected {self.dims[modality]}" + ) + sequence, representation = encoder(x, masks[..., modality]) + # Missing-mask baseline has a zero pooled representation. Explicit + # subtraction makes every main effect zero at that baseline. + baseline_raw = head(representation.new_zeros(representation.shape)) + effect = _center_class_parameters(head(representation) - baseline_raw) + sequences.append(sequence) + pooled.append(representation) + mask_logits.append(mask_head(sequence).squeeze(-1)) + main_effects.append(effect) + + pair_effects: list[torch.Tensor] = [] + pair_penalties: list[torch.Tensor] = [] + for branch, (left_idx, right_idx) in zip(self.pair_branches, PAIR_INDICES): + pair = branch( + sequences[left_idx], + sequences[right_idx], + masks[..., left_idx], + masks[..., right_idx], + anchored=self.config.anchored, + ) + pair_effects.append(pair) + pair_penalties.append(pair.square().mean()) + + main_tensor = torch.stack(main_effects, dim=1) + pair_tensor = torch.stack(pair_effects, dim=1) + params = self.baseline.unsqueeze(0) + main_tensor.sum(dim=1) + pair_tensor.sum(dim=1) + logits = params[:, :3] + probabilities = torch.softmax(logits, dim=-1) + nu_negative = 3.0 * torch.sigmoid(params[:, 3]) + nu_positive = 3.0 * torch.sigmoid(params[:, 4]) + predicted_class = logits.argmax(dim=-1) + hard_intensity = torch.where( + predicted_class == 0, + -nu_negative, + torch.where(predicted_class == 2, nu_positive, torch.zeros_like(nu_positive)), + ) + soft_intensity = probabilities[:, 2] * nu_positive - probabilities[:, 0] * nu_negative + result: dict[str, Any] = { + "logits": logits, + "probabilities": probabilities, + "predicted_class": predicted_class, + "intensity": hard_intensity, + "soft_intensity": soft_intensity, + "nu_negative": nu_negative, + "nu_positive": nu_positive, + "params": params, + "interaction_penalty": torch.stack(pair_penalties).mean(), + "mask_logits": torch.stack(mask_logits, dim=-1), + } + if return_details: + result.update( + { + "baseline": self.baseline.unsqueeze(0).expand(xs[0].shape[0], -1), + "main_effects": main_tensor, + "pair_effects": pair_tensor, + "main_sequences": torch.stack(sequences, dim=1), + } + ) + return result + + +def task_loss( + output: dict[str, Any], + y_cls: torch.Tensor, + y_reg: torch.Tensor, + *, + lambda_interaction: float, + lambda_mask: float, + mask_target: torch.Tensor | None = None, +) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: + """CE + conditional polarity magnitude + low-weight continuous Huber.""" + class_loss = F.cross_entropy(output["logits"], y_cls) + negative = y_reg < 0 + positive = y_reg > 0 + target_mag = torch.abs(y_reg) / 3.0 + magnitude_parts: list[torch.Tensor] = [] + if negative.any(): + magnitude_parts.append( + F.smooth_l1_loss(output["nu_negative"][negative] / 3.0, target_mag[negative]) + ) + if positive.any(): + magnitude_parts.append( + F.smooth_l1_loss(output["nu_positive"][positive] / 3.0, target_mag[positive]) + ) + magnitude_loss = torch.stack(magnitude_parts).mean() if magnitude_parts else class_loss.new_zeros(()) + continuous_loss = F.huber_loss( + output["soft_intensity"] / 3.0, y_reg / 3.0, delta=0.25 + ) + interaction_loss = output["interaction_penalty"] + mask_loss = class_loss.new_zeros(()) + if lambda_mask > 0: + if mask_target is None: + raise ValueError("mask_target is required when the visibility-mask auxiliary loss is enabled") + mask_loss = F.binary_cross_entropy_with_logits( + output["mask_logits"], mask_target.to(output["mask_logits"].dtype) + ) + total = ( + class_loss + + magnitude_loss + + 0.2 * continuous_loss + + lambda_interaction * interaction_loss + + lambda_mask * mask_loss + ) + parts = { + "classification": class_loss, + "conditional_magnitude": magnitude_loss, + "continuous_huber": continuous_loss, + "interaction": interaction_loss, + "visibility_mask": mask_loss, + "total": total, + } + return total, parts diff --git a/submit/final/model/ati_ho_config.py b/submit/final/model/ati_ho_config.py new file mode 100644 index 0000000..de39a9e --- /dev/null +++ b/submit/final/model/ati_ho_config.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from dataclasses import asdict, dataclass + + +@dataclass(frozen=True) +class ATIConfig: + name: str + low_rank: bool = False + cross_attention: bool = False + anchored: bool = True + lambda_interaction: float = 1e-3 + lambda_mask: float = 0.0 + rank: int = 4 + hidden: int = 64 + gru_hidden_per_direction: int = 32 + attention_heads: int = 4 + attention_ffn: int = 128 + eta_init: float = 0.1 + + def to_dict(self) -> dict[str, object]: + return asdict(self) + + +CONFIGS: dict[str, ATIConfig] = { + "A0": ATIConfig(name="A0_main_effects"), + "A1": ATIConfig(name="A1_low_rank_pairs", low_rank=True), + "A2": ATIConfig(name="A2_anchored_pairwise", low_rank=True, cross_attention=True), + "A3": ATIConfig( + name="A3_pairwise_mask_aux", low_rank=True, cross_attention=True, lambda_mask=0.05 + ), + "D0": ATIConfig( + name="D0_unanchored_diagnostic", low_rank=True, cross_attention=True, anchored=False + ), +} diff --git a/submit/final/model/c0.py b/submit/final/model/c0.py new file mode 100644 index 0000000..a276a9d --- /dev/null +++ b/submit/final/model/c0.py @@ -0,0 +1,57 @@ +"""C0: observed statistics and mask baseline from E题V2, table 5.9.""" +from __future__ import annotations + +import numpy as np +from sklearn.linear_model import LogisticRegression, Ridge + +MODALITIES = ("text", "audio", "vision") + + +def sample_statistics(arrays: dict[str, np.ndarray], mask: np.ndarray) -> np.ndarray: + """Mean, standard deviation, missing fraction and longest gap per modality.""" + mask = np.asarray(mask, dtype=bool) + if mask.ndim != 3 or mask.shape[-1] != 3: + raise ValueError("mask must have shape (N, T, 3)") + parts = [] + for index, name in enumerate(MODALITIES): + x = np.asarray(arrays[name], dtype=np.float32) + if x.shape[:2] != mask.shape[:2]: + raise ValueError(f"{name}: feature and mask shapes disagree") + visible = mask[:, :, index] + count = visible.sum(axis=1, keepdims=True) + mean = (x * visible[:, :, None]).sum(axis=1) / np.maximum(count, 1) + variance = (((x - mean[:, None, :]) ** 2) * visible[:, :, None]).sum(axis=1) / np.maximum(count, 1) + missing = 1.0 - visible.mean(axis=1, keepdims=True) + max_gap = [] + for row in visible: + longest = current = 0 + for observed in row: + current = 0 if observed else current + 1 + longest = max(longest, current) + max_gap.append(longest / max(len(row), 1)) + parts.extend((mean, np.sqrt(variance), missing, np.asarray(max_gap, np.float32)[:, None])) + return np.concatenate(parts, axis=1).astype(np.float32) + + +class C0: + """Logistic polarity classifier and Ridge intensity regressor.""" + + def __init__(self) -> None: + self.classifier = LogisticRegression(C=0.05, max_iter=2500, random_state=20260924) + self.regressor = Ridge(alpha=25.0) + + def fit(self, arrays: dict[str, np.ndarray], mask: np.ndarray, + polarity: np.ndarray, intensity: np.ndarray) -> "C0": + features = sample_statistics(arrays, mask) + self.classifier.fit(features, polarity) + self.regressor.fit(features, intensity) + return self + + def predict(self, arrays: dict[str, np.ndarray], mask: np.ndarray) -> dict[str, np.ndarray]: + features = sample_statistics(arrays, mask) + probabilities = np.zeros((len(features), 3), np.float64) + probabilities[:, self.classifier.classes_] = self.classifier.predict_proba(features) + return { + "probabilities": probabilities, + "intensity": np.clip(self.regressor.predict(features), -3.0, 3.0), + } diff --git a/submit/final/model/c1.py b/submit/final/model/c1.py new file mode 100644 index 0000000..ee40481 --- /dev/null +++ b/submit/final/model/c1.py @@ -0,0 +1,9 @@ +"""C1: masked BiGRU, without probabilistic completion or explicit gates.""" +from .crg import CRG + + +class C1(CRG): + def __init__(self, **kwargs): + super().__init__(use_imputer=False, use_joint_draws=False, + use_final_gate=False, use_source_attention=False, + reliability_update=False, use_low_rank=False, **kwargs) diff --git a/submit/final/model/c2.py b/submit/final/model/c2.py new file mode 100644 index 0000000..022ed8d --- /dev/null +++ b/submit/final/model/c2.py @@ -0,0 +1,9 @@ +"""C2: Gaussian posterior mean completion, without trajectory integration.""" +from .crg import CRG + + +class C2(CRG): + def __init__(self, **kwargs): + super().__init__(use_imputer=True, use_joint_draws=False, + use_final_gate=False, use_source_attention=False, + reliability_update=False, use_low_rank=False, **kwargs) diff --git a/submit/final/model/c3.py b/submit/final/model/c3.py new file mode 100644 index 0000000..9d8e0d5 --- /dev/null +++ b/submit/final/model/c3.py @@ -0,0 +1,9 @@ +"""C3: joint trajectory integration and final reliability/content fusion.""" +from .crg import CRG + + +class C3(CRG): + def __init__(self, **kwargs): + super().__init__(use_imputer=True, use_joint_draws=True, + use_final_gate=True, use_source_attention=False, + reliability_update=False, use_low_rank=False, **kwargs) diff --git a/submit/final/model/c4.py b/submit/final/model/c4.py new file mode 100644 index 0000000..6f576e9 --- /dev/null +++ b/submit/final/model/c4.py @@ -0,0 +1,9 @@ +"""C4: C3 with bounded source attention and a null source.""" +from .crg import CRG + + +class C4(CRG): + def __init__(self, **kwargs): + super().__init__(use_imputer=True, use_joint_draws=True, + use_final_gate=True, use_source_attention=True, + reliability_update=False, use_low_rank=False, **kwargs) diff --git a/submit/final/model/c5.py b/submit/final/model/c5.py new file mode 100644 index 0000000..568be97 --- /dev/null +++ b/submit/final/model/c5.py @@ -0,0 +1,9 @@ +"""C5: C4 with reliability-modulated recurrent updates.""" +from .crg import CRG + + +class C5(CRG): + def __init__(self, **kwargs): + super().__init__(use_imputer=True, use_joint_draws=True, + use_final_gate=True, use_source_attention=True, + reliability_update=True, use_low_rank=False, **kwargs) diff --git a/submit/final/model/c6.py b/submit/final/model/c6.py new file mode 100644 index 0000000..cf905a4 --- /dev/null +++ b/submit/final/model/c6.py @@ -0,0 +1,9 @@ +"""C6: C5 with the optional rank-four CP interaction residual enabled.""" +from .crg import CRG + + +class C6(CRG): + def __init__(self, **kwargs): + super().__init__(use_imputer=True, use_joint_draws=True, + use_final_gate=True, use_source_attention=True, + reliability_update=True, use_low_rank=True, **kwargs) diff --git a/submit/final/model/c6_no_distance.py b/submit/final/model/c6_no_distance.py new file mode 100644 index 0000000..2bd44d5 --- /dev/null +++ b/submit/final/model/c6_no_distance.py @@ -0,0 +1,7 @@ +"""C6 diagnostic: disable uncertainty distance and span penalties.""" +from .c6 import C6 + + +class C6NoDistance(C6): + def __init__(self, **kwargs): + super().__init__(reliability_hparams=(0.5, 0.05, 0.0, 0.0), **kwargs) diff --git a/submit/final/model/c6_no_reconstruction.py b/submit/final/model/c6_no_reconstruction.py new file mode 100644 index 0000000..7801634 --- /dev/null +++ b/submit/final/model/c6_no_reconstruction.py @@ -0,0 +1,8 @@ +"""C6 diagnostic: omit auxiliary hidden-feature reconstruction while fitting.""" +from .c6 import C6 + + +class C6NoReconstruction(C6): + """Use the C6 forward pass and set reconstruction loss weight to zero.""" + + reconstruction_loss_weight = 0.0 diff --git a/submit/final/model/c6_pointmask.py b/submit/final/model/c6_pointmask.py new file mode 100644 index 0000000..4259c82 --- /dev/null +++ b/submit/final/model/c6_pointmask.py @@ -0,0 +1,8 @@ +"""C6 diagnostic: train with independent point masks instead of spans.""" +from .c6 import C6 + + +class C6PointMask(C6): + """Use the C6 forward pass with mask_kind='point' during fitting.""" + + training_mask_kind = "point" diff --git a/submit/final/model/c7_distill.py b/submit/final/model/c7_distill.py new file mode 100644 index 0000000..219fb40 --- /dev/null +++ b/submit/final/model/c7_distill.py @@ -0,0 +1,41 @@ +"""C7 distillation-only branch: C6 architecture plus teacher loss.""" +from __future__ import annotations + +import math + +import numpy as np +import torch +from torch.nn import functional as F + +from .c6 import C6 + +DISTILL_TEMPERATURE = 2.0 +DISTILL_WEIGHT = 0.1 + + +class C7Distill(C6): + """Inference uses C6; training adds weighted teacher distillation.""" + + +def distillation_per_sample(student: dict, teacher: dict, + original: np.ndarray, current: np.ndarray) -> torch.Tensor: + """Entropy/retention-weighted KL and score term for Q2 distillation.""" + temp = DISTILL_TEMPERATURE + p_teacher = teacher["tempered_probs_by_path"].mean(dim=0).detach().clamp_min(1e-8) + p_student = student["tempered_probs_by_path"].mean(dim=0).clamp_min(1e-8) + entropy = -(p_teacher * p_teacher.log()).sum(dim=-1) + confidence_weight = (1.0 - entropy / math.log(3.0)).clamp(0.0, 1.0) + orig_t = torch.as_tensor(original, device=p_teacher.device, dtype=torch.float32) + curr_t = torch.as_tensor(current, device=p_teacher.device, dtype=torch.float32) + retained = [] + for modality in range(3): + denominator = orig_t[:, :, modality].sum(dim=1) + ratio = (orig_t[:, :, modality] * curr_t[:, :, modality]).sum(dim=1) / denominator.clamp_min(1.0) + retained.append(torch.where(denominator > 0, ratio, torch.ones_like(ratio))) + weight = confidence_weight * torch.stack(retained, dim=-1).mean(dim=-1) + kl = (p_teacher * (p_teacher.log() - p_student.log())).sum(dim=-1) * temp * temp + teacher_score = teacher["mixed_score"].detach() + student_score = student["mixed_score"] + regression = F.huber_loss((teacher_score - student_score) / 3.0, + torch.zeros_like(teacher_score), reduction="none", delta=0.25) + return weight * (kl + regression) diff --git a/submit/final/model/c7_group.py b/submit/final/model/c7_group.py new file mode 100644 index 0000000..d6590c2 --- /dev/null +++ b/submit/final/model/c7_group.py @@ -0,0 +1,32 @@ +"""C7 group-risk-only branch: C6 architecture plus smooth worst-group loss.""" +from __future__ import annotations + +import numpy as np +import torch + +from .c6 import C6 + + +class C7Group(C6): + """Inference uses C6; training adds smooth worst-group risk.""" + + +def smooth_group_risk(losses: torch.Tensor, group_ids: np.ndarray, + lambda_group: float = 0.1, + group_temperature: float = 0.05) -> torch.Tensor: + """Match the selected group penalty from the Q2 training protocol.""" + if group_temperature <= 0 or not 0 <= lambda_group <= 1: + raise ValueError("invalid group risk parameters") + groups = torch.as_tensor(group_ids, device=losses.device, dtype=torch.long) + if groups.shape != losses.shape: + raise ValueError("group_ids must match per-sample losses") + group_losses, priors = [], [] + for group in torch.unique(groups): + selected = groups == group + group_losses.append(losses[selected].mean()) + priors.append(selected.float().mean()) + values = torch.stack(group_losses) + prior = torch.stack(priors).clamp_min(1e-8) + expected = (prior * values).sum() + worst = group_temperature * torch.logsumexp(torch.log(prior) + values / group_temperature, dim=0) + return (1.0 - lambda_group) * expected + lambda_group * worst diff --git a/submit/final/model/crg.py b/submit/final/model/crg.py new file mode 100644 index 0000000..bb6ae36 --- /dev/null +++ b/submit/final/model/crg.py @@ -0,0 +1,556 @@ +"""Structured Gaussian imputation and reliability-aware CRG sequence model.""" +from __future__ import annotations + +import math +from typing import Sequence + +import torch +from torch import nn +from torch.nn import functional as F + +MODALITIES = ("text", "audio", "vision") +INPUT_DIMS = (768, 74, 35) +HIDDEN = 32 +SHARED_STATE = 8 +PRIVATE_STATE = 4 +STATE_DIM = SHARED_STATE + len(MODALITIES) * PRIVATE_STATE + + +def _inv_softplus(value: float) -> float: + return math.log(math.expm1(value)) + + +class StructuredGaussianImputer(nn.Module): + """Linear-Gaussian shared/private state model with exact block-Gaussian inference. + + The state is [shared(8), text-private(4), audio-private(4), vision-private(4)]. + Each modality emits from the shared state and its own private state only. The + filtering likelihood uses the matrix determinant lemma, retaining its log-det + normalization without forming a covariance matrix in observation space. + """ + + def __init__(self, input_dims: Sequence[int] = INPUT_DIMS) -> None: + super().__init__() + self.input_dims = tuple(int(x) for x in input_dims) + self.state_dim = STATE_DIM + transition_mask = torch.zeros(STATE_DIM, STATE_DIM) + blocks = [slice(0, SHARED_STATE)] + [ + slice(SHARED_STATE + i * PRIVATE_STATE, SHARED_STATE + (i + 1) * PRIVATE_STATE) + for i in range(len(MODALITIES)) + ] + for block in blocks: + transition_mask[block, block] = 1.0 + self.register_buffer("transition_mask", transition_mask) + self.transition_raw = nn.Parameter(0.8 * torch.eye(STATE_DIM)) + self.mu0 = nn.Parameter(torch.zeros(STATE_DIM)) + self.pi0_raw = nn.Parameter(torch.full((STATE_DIM,), _inv_softplus(1.0))) + self.q_raw = nn.Parameter(torch.full((STATE_DIM,), _inv_softplus(0.08))) + self.emission_raw = nn.ParameterList() + self.biases = nn.ParameterList() + self.r_raw = nn.ParameterList() + for index, dim in enumerate(self.input_dims): + mask = torch.zeros(dim, STATE_DIM) + mask[:, :SHARED_STATE] = 1.0 + private_start = SHARED_STATE + index * PRIVATE_STATE + mask[:, private_start:private_start + PRIVATE_STATE] = 1.0 + self.register_buffer(f"emission_mask_{index}", mask) + self.emission_raw.append(nn.Parameter(torch.randn(dim, STATE_DIM) * 0.025)) + self.biases.append(nn.Parameter(torch.zeros(dim))) + self.r_raw.append(nn.Parameter(torch.full((dim,), _inv_softplus(0.5)))) + + def _transition(self) -> torch.Tensor: + matrix = self.transition_raw * self.transition_mask + norm = torch.linalg.matrix_norm(matrix, ord=2).clamp_min(1e-8) + return matrix * torch.clamp(0.98 / norm, max=1.0) + + def _covariances(self) -> tuple[torch.Tensor, torch.Tensor]: + eye = torch.eye(self.state_dim, device=self.mu0.device, dtype=self.mu0.dtype) + p0 = torch.diag(F.softplus(self.pi0_raw) + 1e-4) + 1e-5 * eye + q = torch.diag(F.softplus(self.q_raw) + 1e-4) + 1e-5 * eye + return p0, q + + def emissions(self) -> list[torch.Tensor]: + return [raw * getattr(self, f"emission_mask_{i}") for i, raw in enumerate(self.emission_raw)] + + def _filter( + self, + xs: Sequence[torch.Tensor], + observed: torch.Tensor, + *, + calculate_log_likelihood: bool, + retain_states: bool, + ) -> tuple[torch.Tensor | None, dict[str, list[torch.Tensor]] | None]: + # xs[m]: [B,T,Dm], observed: [B,T,3] + batch, steps, _ = observed.shape + transition = self._transition() + p0, process_noise = self._covariances() + emissions = self.emissions() + noise = [F.softplus(x) + 1e-4 for x in self.r_raw] + mu_prior = self.mu0.expand(batch, -1) + p_prior = p0.expand(batch, -1, -1) + total_nll = torch.zeros(batch, device=observed.device, dtype=mu_prior.dtype) + prior_means: list[torch.Tensor] = [] + prior_covs: list[torch.Tensor] = [] + filtered_means: list[torch.Tensor] = [] + filtered_covs: list[torch.Tensor] = [] + + for t in range(steps): + if retain_states: + prior_means.append(mu_prior) + prior_covs.append(p_prior) + p_chol = torch.linalg.cholesky(p_prior + 1e-6 * torch.eye(self.state_dim, device=p_prior.device)) + p_inv = torch.cholesky_inverse(p_chol) + information_parts: list[torch.Tensor] = [] + vector_parts: list[torch.Tensor] = [] + quadratic_parts: list[torch.Tensor] = [] + logdet_r = torch.zeros(batch, device=p_prior.device, dtype=p_prior.dtype) + n_observed = torch.zeros_like(logdet_r) + for m, (x, emission, variance) in enumerate(zip(xs, emissions, noise)): + active = observed[:, t, m].to(dtype=mu_prior.dtype) + weights = active[:, None] / variance[None, :] + centered = x[:, t] - self.biases[m] + residual = centered - mu_prior @ emission.T + information_parts.append(torch.einsum("di,bd,dj->bij", emission, weights, emission)) + vector_parts.append((residual * weights) @ emission) + quadratic_parts.append((residual.square() * weights).sum(dim=-1)) + logdet_r = logdet_r + active * torch.log(variance).sum() + n_observed = n_observed + active * x.shape[-1] + information = torch.stack(information_parts).sum(dim=0) + innovation = torch.stack(vector_parts).sum(dim=0) + precision = p_inv + information + precision_chol = torch.linalg.cholesky(precision + 1e-6 * torch.eye(self.state_dim, device=precision.device)) + p_filtered = torch.cholesky_inverse(precision_chol) + mu_filtered = mu_prior + torch.einsum("bij,bj->bi", p_filtered, innovation) + if calculate_log_likelihood: + logdet_p = 2.0 * torch.log(torch.diagonal(p_chol, dim1=-2, dim2=-1)).sum(dim=-1) + logdet_precision = 2.0 * torch.log(torch.diagonal(precision_chol, dim1=-2, dim2=-1)).sum(dim=-1) + quad = torch.stack(quadratic_parts).sum(dim=0) + correction = torch.einsum("bi,bij,bj->b", innovation, p_filtered, innovation) + log_likelihood = logdet_r + logdet_p + logdet_precision + (quad - correction).clamp_min(0.0) + log_likelihood = log_likelihood + n_observed * math.log(2.0 * math.pi) + total_nll = total_nll + 0.5 * log_likelihood + if retain_states: + filtered_means.append(mu_filtered) + filtered_covs.append(p_filtered) + mu_prior = mu_filtered @ transition.T + p_prior = transition @ p_filtered @ transition.T + process_noise + + states = None + if retain_states: + states = { + "prior_mean": prior_means, + "prior_cov": prior_covs, + "filtered_mean": filtered_means, + "filtered_cov": filtered_covs, + "transition": [transition], + } + return (total_nll if calculate_log_likelihood else None), states + + def observed_nll(self, xs: Sequence[torch.Tensor], observed: torch.Tensor) -> torch.Tensor: + """Exact observed-data Gaussian NLL, including covariance log determinants.""" + nll, _ = self._filter(xs, observed, calculate_log_likelihood=True, retain_states=False) + assert nll is not None + return nll + + @staticmethod + def _draw(mean: torch.Tensor, covariance: torch.Tensor, paths: int) -> torch.Tensor: + chol = torch.linalg.cholesky(covariance + 1e-5 * torch.eye(covariance.shape[-1], device=covariance.device)) + noise = torch.randn((paths, *mean.shape), dtype=mean.dtype, device=mean.device) + return mean.unsqueeze(0) + torch.einsum("bij,kbj->kbi", chol, noise) + + @torch.no_grad() + def complete( + self, + xs: Sequence[torch.Tensor], + observed: torch.Tensor, + paths: int, + *, + joint_draws: bool, + ) -> tuple[list[torch.Tensor], list[torch.Tensor]]: + """RTS smooth, draw joint latent trajectories, then draw missing emissions.""" + _, stored = self._filter(xs, observed, calculate_log_likelihood=False, retain_states=True) + assert stored is not None + fm, fc = stored["filtered_mean"], stored["filtered_cov"] + pm, pc = stored["prior_mean"], stored["prior_cov"] + transition = stored["transition"][0] + steps = len(fm) + smoother_gains: list[torch.Tensor] = [torch.empty(0, device=observed.device)] * max(0, steps - 1) + smooth_cov: list[torch.Tensor] = [torch.empty(0, device=observed.device)] * steps + smooth_cov[-1] = fc[-1] + for t in range(steps - 2, -1, -1): + next_chol = torch.linalg.cholesky(pc[t + 1] + 1e-6 * torch.eye(self.state_dim, device=observed.device)) + gain = torch.cholesky_solve((fc[t] @ transition.T).transpose(-1, -2), next_chol).transpose(-1, -2) + smoother_gains[t] = gain + smooth_cov[t] = fc[t] + gain @ (smooth_cov[t + 1] - pc[t + 1]) @ gain.transpose(-1, -2) + smooth_cov[t] = 0.5 * (smooth_cov[t] + smooth_cov[t].transpose(-1, -2)) + + if joint_draws: + state = torch.empty((paths, observed.shape[0], steps, self.state_dim), device=observed.device, dtype=fm[0].dtype) + state[:, :, -1] = self._draw(fm[-1], fc[-1], paths) + for t in range(steps - 2, -1, -1): + gain = smoother_gains[t] + conditional_mean = fm[t].unsqueeze(0) + torch.einsum( + "bij,kbj->kbi", gain, state[:, :, t + 1] - pm[t + 1].unsqueeze(0) + ) + conditional_cov = fc[t] - gain @ pc[t + 1] @ gain.transpose(-1, -2) + conditional_cov = 0.5 * (conditional_cov + conditional_cov.transpose(-1, -2)) + chol = torch.linalg.cholesky(conditional_cov + 1e-5 * torch.eye(self.state_dim, device=observed.device)) + eps = torch.randn_like(conditional_mean) + state[:, :, t] = conditional_mean + torch.einsum("bij,kbj->kbi", chol, eps) + else: + means = torch.stack(fm, dim=1) + covs = torch.stack(smooth_cov, dim=1) + smoothed_means = [fm[-1]] * steps + smoothed_means[-1] = fm[-1] + for t in range(steps - 2, -1, -1): + smoothed_means[t] = fm[t] + torch.einsum( + "bij,bj->bi", smoother_gains[t], smoothed_means[t + 1] - pm[t + 1] + ) + state = torch.stack(smoothed_means, dim=1).unsqueeze(0).expand(paths, -1, -1, -1) + + completed: list[torch.Tensor] = [] + variances: list[torch.Tensor] = [] + for m, (x, emission) in enumerate(zip(xs, self.emissions())): + mean = torch.einsum("kbti,di->kbtd", state, emission) + self.biases[m] + if joint_draws: + noise = torch.randn_like(mean) * torch.sqrt(F.softplus(self.r_raw[m]) + 1e-4) + draws = mean + noise + else: + draws = mean + visible = observed[:, :, m].unsqueeze(0).unsqueeze(-1) + completed.append(torch.where(visible, x.unsqueeze(0), draws)) + projected_cov = torch.einsum("di,btij,dj->btd", emission, torch.stack(smooth_cov, dim=1), emission) + variance = projected_cov + (F.softplus(self.r_raw[m]) + 1e-4) + variances.append(torch.where(observed[:, :, m, None], torch.zeros_like(variance), variance.clamp_min(1e-6))) + return completed, variances + + +class ReliabilityGRU(nn.Module): + """One-layer BiGRU with directional time decay and rho-scaled updates.""" + + def __init__(self, input_dim: int, hidden: int = 16) -> None: + super().__init__() + self.hidden = hidden + self.x_proj = nn.Linear(input_dim, 3 * hidden) + self.h_proj = nn.Linear(hidden, 2 * hidden, bias=False) + self.candidate_h = nn.Linear(hidden, hidden, bias=False) + self.decay_raw = nn.Parameter(torch.full((hidden,), -3.0)) + + def _one_direction( + self, + x: torch.Tensor, + rho: torch.Tensor, + distance: torch.Tensor, + reverse: bool, + reliability_update: bool, + ) -> torch.Tensor: + batch, steps, _ = x.shape + state = torch.zeros(batch, self.hidden, dtype=x.dtype, device=x.device) + x_parts = self.x_proj(x).chunk(3, dim=-1) + output: list[torch.Tensor | None] = [None] * steps + indices = range(steps - 1, -1, -1) if reverse else range(steps) + for t in indices: + if reliability_update: + decay = torch.exp(-F.softplus(self.decay_raw)[None, :] * distance[:, t:t + 1]) + decayed_state = decay * state + else: + decayed_state = state + hz, hr = self.h_proj(decayed_state).chunk(2, dim=-1) + z = torch.sigmoid(x_parts[0][:, t] + hz) + r = torch.sigmoid(x_parts[1][:, t] + hr) + candidate = torch.tanh(x_parts[2][:, t] + self.candidate_h(r * decayed_state)) + effective_z = rho[:, t:t + 1] * z if reliability_update else z + state = (1.0 - effective_z) * decayed_state + effective_z * candidate + output[t] = state + return torch.stack([v for v in output if v is not None], dim=1) + + def forward( + self, + x: torch.Tensor, + rho: torch.Tensor, + dminus: torch.Tensor, + dplus: torch.Tensor, + reliability_update: bool, + ) -> torch.Tensor: + if not reliability_update: + rho = torch.ones_like(rho) + return torch.cat(( + self._one_direction(x, rho, dminus, False, reliability_update), + self._one_direction(x, rho, dplus, True, reliability_update), + ), dim=-1) + + +class CRG(nn.Module): + """Quality-aware multimodal sequence predictor for a configured ablation.""" + + def __init__( + self, + imputer: StructuredGaussianImputer | None = None, + input_dims: Sequence[int] = INPUT_DIMS, + *, + use_imputer: bool = True, + use_joint_draws: bool = True, + use_final_gate: bool = True, + use_source_attention: bool = True, + reliability_update: bool = True, + use_low_rank: bool = True, + reliability_hparams: tuple[float, float, float, float] = (0.5, 0.05, 0.05, 0.05), + ) -> None: + super().__init__() + self.use_imputer = use_imputer + self.use_joint_draws = use_joint_draws + self.use_final_gate = use_final_gate + self.use_source_attention = use_source_attention + self.reliability_update = reliability_update + self.use_low_rank = use_low_rank + self.imputer = imputer if imputer is not None else StructuredGaussianImputer(input_dims) + self.projections = nn.ModuleList( + nn.Sequential(nn.Linear(d, HIDDEN), nn.LayerNorm(HIDDEN), nn.GELU()) for d in input_dims + ) + recurrent_input = HIDDEN + 13 + self.temporal = nn.ModuleList(ReliabilityGRU(recurrent_input, 16) for _ in MODALITIES) + rho_imp, lambda_u, lambda_gap, lambda_span = reliability_hparams + if not 0.0 < rho_imp < 1.0 or min(lambda_u, lambda_gap, lambda_span) < 0.0: + raise ValueError("reliability requires 0 tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + # Time positions are valid sequence locations even when all three sources are missing. + batch, steps, modalities = observed.shape + device = observed.device + positions = torch.arange(steps, device=device).view(1, steps).expand(batch, -1) + previous = torch.full((batch, modalities), -1, device=device, dtype=torch.long) + before, before_edge = [], [] + for t in range(steps): + before_edge.append(previous < 0) + before.append(torch.where(previous < 0, torch.ones_like(previous, dtype=torch.float32), (t - previous).float() / max(1, steps - 1))) + previous = torch.where(observed[:, t], torch.full_like(previous, t), previous) + following = torch.full((batch, modalities), steps, device=device, dtype=torch.long) + after, after_edge = [None] * steps, [None] * steps + for t in range(steps - 1, -1, -1): + after_edge[t] = following >= steps + after[t] = torch.where(following >= steps, torch.ones_like(following, dtype=torch.float32), (following - t).float() / max(1, steps - 1)) + following = torch.where(observed[:, t], torch.full_like(following, t), following) + dminus = torch.stack(before, dim=1) + dplus = torch.stack([x for x in after if x is not None], dim=1) + edge_minus = torch.stack(before_edge, dim=1) + edge_plus = torch.stack([x for x in after_edge if x is not None], dim=1) + dminus = torch.where(observed, torch.zeros_like(dminus), dminus) + dplus = torch.where(observed, torch.zeros_like(dplus), dplus) + edge_minus = edge_minus & ~observed + edge_plus = edge_plus & ~observed + missing = ~observed + left_run = torch.zeros((batch, steps, modalities), device=device, dtype=torch.float32) + run = torch.zeros((batch, modalities), device=device, dtype=torch.float32) + for t in range(steps): + run = torch.where(missing[:, t], run + 1.0, torch.zeros_like(run)) + left_run[:, t] = run + right_run = torch.zeros_like(left_run) + run.zero_() + for t in range(steps - 1, -1, -1): + run = torch.where(missing[:, t], run + 1.0, torch.zeros_like(run)) + right_run[:, t] = run + span = torch.where(missing, (left_run + right_run - 1.0) / max(1, steps), torch.zeros_like(left_run)) + return dminus, dplus, span, torch.stack((edge_minus, edge_plus), dim=-1).float() + + def _reliability( + self, observed: torch.Tensor, uncertainty: torch.Tensor, + ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + dminus, dplus, span, edges = self._gap_features(observed) + gap = torch.minimum(dminus, dplus) + gap = torch.where(observed, torch.zeros_like(gap), gap) + u = torch.where(observed, torch.zeros_like(uncertainty), uncertainty).clamp_min(0.0) + qstar = observed.float() # External Q2 quality is unavailable: q*=1 only for visible rows; J=0. + rho_missing = self.rho_imp.clamp(1e-4, 0.999) * torch.exp( + -self.rel_u[None, None, :] * u + -self.rel_gap[None, None, :] * gap + -self.rel_span[None, None, :] * span + ) + rho = torch.where(observed, qstar, rho_missing).clamp(1e-4, 1.0) + return rho, u, gap, span, dminus, dplus, torch.cat((qstar.unsqueeze(-1), torch.zeros_like(qstar).unsqueeze(-1), edges), dim=-1) + + def _cross_source(self, hidden: torch.Tensor, rho: torch.Tensor) -> torch.Tensor: + # hidden [B,T,M,H]; each query reads every legal time in each other source. + batch, steps, modalities, width = hidden.shape + outputs = [] + q = self.query(hidden) + k = self.key(hidden) + v = torch.tanh(self.value(hidden)) + loc = torch.arange(steps, device=hidden.device) + relative_index = (loc[None, :] - loc[:, None] + 49).clamp(0, 98) + relative = self.relative_bias(relative_index).squeeze(-1) + for target in range(modalities): + numerator = torch.zeros((batch, steps, width), device=hidden.device, dtype=hidden.dtype) + denominator = torch.ones((batch, steps, 1), device=hidden.device, dtype=hidden.dtype) + for source in range(modalities): + if source == target: + continue + raw = torch.matmul(q[:, :, target], k[:, :, source].transpose(-1, -2)) / math.sqrt(width) + scores = 2.0 * torch.tanh(raw + relative) + base = 1.0 / (max(1, modalities - 1) * steps) + weights = base * rho[:, None, :, source] * torch.exp(scores.clamp(-2.0, 2.0)) + numerator = numerator + torch.matmul(weights, v[:, :, source]) + denominator = denominator + weights.sum(dim=-1, keepdim=True) + context = numerator / denominator + eta = torch.sigmoid(self.cross_eta_logit) + outputs.append(torch.tanh(self.cross_base(hidden[:, :, target]) + eta * self.cross_out(context))) + return torch.stack(outputs, dim=2) + + def _low_rank_residual(self, gated: torch.Tensor) -> torch.Tensor: + # Linear CP factors use [1; z_m] and subtract their constant all-zero term. + batch, steps, modalities, width = gated.shape + one = torch.ones((batch, steps, 1), device=gated.device, dtype=gated.dtype) + products = torch.ones((batch, steps, 4), device=gated.device, dtype=gated.dtype) + constant = torch.ones(4, device=gated.device, dtype=gated.dtype) + for m in range(modalities): + factor = self.cp_factors[m](torch.cat((one, gated[:, :, m]), dim=-1)) + products = products * factor + zero_input = torch.zeros((1, 1, width + 1), device=gated.device, dtype=gated.dtype) + zero_input[..., 0] = 1.0 + constant = constant * self.cp_factors[m](zero_input)[0, 0] + residual = (products - constant) @ self.cp_output + return torch.sigmoid(self.low_rank_eta_logit) * self.low_rank_output(torch.tanh(residual)) + + def forward( + self, + xs: Sequence[torch.Tensor], + observed_mask: torch.Tensor, + *, + paths: int = 4, + joint_draws: bool | None = None, + ) -> dict[str, torch.Tensor | list[torch.Tensor]]: + batch, steps, modalities = observed_mask.shape + if joint_draws is None: + joint_draws = self.use_joint_draws + if self.use_imputer: + completed, variance = self.imputer.complete(xs, observed_mask, paths, joint_draws=joint_draws) + else: + completed = [torch.where(observed_mask[:, :, m, None], x, torch.zeros_like(x)).unsqueeze(0) for m, x in enumerate(xs)] + variance = [torch.zeros_like(x) for x in xs] + paths = completed[0].shape[0] + uncertainty_parts = [v.mean(dim=-1) for v in variance] + uncertainty = torch.stack(uncertainty_parts, dim=-1) + rho, u, gap, span, dminus, dplus, quality_fields = self._reliability(observed_mask, uncertainty) + position = torch.linspace(0.0, 1.0, steps, device=observed_mask.device, dtype=xs[0].dtype) + pe = torch.stack((torch.sin(2 * math.pi * position), torch.cos(2 * math.pi * position), + torch.sin(4 * math.pi * position), torch.cos(4 * math.pi * position)), dim=-1) + encoded_paths: list[torch.Tensor] = [] + reconstructed_paths: list[list[torch.Tensor]] = [] + logits_paths: list[torch.Tensor] = [] + beta_paths: list[torch.Tensor] = [] + fusion_weight_paths: list[torch.Tensor] = [] + null_weight_paths: list[torch.Tensor] = [] + time_pool_weight_paths: list[torch.Tensor] = [] + for path_index in range(paths): + enc = [projection(completed[m][path_index]) for m, projection in enumerate(self.projections)] + hmods, reconstruction = [], [] + for m, encoder in enumerate(self.temporal): + if self.use_final_gate or self.use_source_attention or self.reliability_update: + scalar = torch.cat((observed_mask[:, :, m:m + 1].float(), quality_fields[:, :, m], + torch.log1p(u[:, :, m:m + 1]), dminus[:, :, m:m + 1], + dplus[:, :, m:m + 1], span[:, :, m:m + 1], + pe.unsqueeze(0).expand(batch, -1, -1)), dim=-1) + else: + # C1/C2 receive only the visibility mask and legal position code. + scalar = torch.zeros((batch, steps, 13), dtype=pe.dtype, device=pe.device) + scalar[:, :, 0] = observed_mask[:, :, m].float() + scalar[:, :, -4:] = pe.unsqueeze(0) + # q*, J_Q, edge flags, directional gaps, uncertainty and span are explicit. + seq = torch.cat((enc[m], scalar), dim=-1) + h = encoder(seq, rho[:, :, m], dminus[:, :, m], dplus[:, :, m], self.reliability_update) + hmods.append(h) + reconstruction.append(self.reconstruction_heads[m](h)) + hidden = torch.stack(hmods, dim=2) + if self.use_source_attention: + enhanced = self._cross_source(hidden, rho) + else: + enhanced = hidden + if self.use_final_gate: + content = 2.0 * torch.tanh(self.content_score(enhanced).squeeze(-1)) + weights_unnorm = rho * torch.exp(content.clamp(-2.0, 2.0)) + denom = 1.0 + weights_unnorm.sum(dim=-1, keepdim=True) + alpha = weights_unnorm / denom + null_alpha = 1.0 / denom.squeeze(-1) + gated = enhanced * alpha.unsqueeze(-1) + fused = gated.sum(dim=2) + null_alpha.unsqueeze(-1) * self.null_expert + else: + if self.use_imputer: + alpha = torch.full_like(observed_mask.float(), 1.0 / modalities) + else: + alpha = observed_mask.float() / observed_mask.float().sum(dim=-1, keepdim=True).clamp_min(1.0) + null_alpha = torch.zeros((batch, steps), device=observed_mask.device, dtype=alpha.dtype) + fused = (enhanced * alpha.unsqueeze(-1)).sum(dim=2) + gated = enhanced * alpha.unsqueeze(-1) + if self.use_low_rank: + fused = fused + self._low_rank_residual(gated) + pool_logits = 2.0 * torch.tanh(self.pool_score(torch.tanh(self.pool_hidden(fused))).squeeze(-1)) + pool_weight = torch.softmax(pool_logits, dim=1) + pooled = (pool_weight.unsqueeze(-1) * fused).sum(dim=1) + missing_rate = 1.0 - observed_mask.float().mean(dim=1) + mean_rho = rho.mean(dim=1) if (self.use_final_gate or self.use_source_attention or self.reliability_update) else observed_mask.float().mean(dim=1) + max_gap = gap.max(dim=1).values + max_span = span.max(dim=1).values + edge_rate = self._gap_features(observed_mask)[3].mean(dim=1).reshape(batch, -1) + stats = torch.cat((missing_rate, mean_rho, max_gap, max_span, edge_rate), dim=-1) + representation = torch.cat((pooled, stats), dim=-1) + feature = self.head(representation) + logits_paths.append(self.classifier(feature)) + mean_fraction = torch.sigmoid(self.magnitude_mean(feature)).clamp(1e-4, 1.0 - 1e-4) + concentration = F.softplus(self.concentration_raw).clamp_min(1e-3) + alpha_beta = torch.stack((mean_fraction * concentration, (1.0 - mean_fraction) * concentration), dim=-1) + beta_paths.append(alpha_beta) + reconstructed_paths.append(reconstruction) + encoded_paths.append(hidden) + fusion_weight_paths.append(alpha) + null_weight_paths.append(null_alpha) + time_pool_weight_paths.append(pool_weight) + class_logits = torch.stack(logits_paths, dim=0) + beta_params = torch.stack(beta_paths, dim=0) + class_probs_by_path = torch.softmax(class_logits, dim=-1) + beta_mean = beta_params[..., 0] / beta_params.sum(dim=-1) + conditional_mean = 3.0 * (class_probs_by_path[..., 2] * beta_mean[..., 1] - class_probs_by_path[..., 0] * beta_mean[..., 0]) + return { + "class_logits": class_logits, + "class_probs_by_path": class_probs_by_path, + "class_probs": class_probs_by_path.mean(dim=0), + "tempered_probs_by_path": torch.softmax(class_logits / 2.0, dim=-1), + "beta_params": beta_params, + "beta_mean": beta_mean, + "mixed_score": conditional_mean.mean(dim=0), + "reconstructions": [torch.stack([reconstructed_paths[k][m] for k in range(paths)], dim=0) for m in range(modalities)], + "reliability": rho, + "imputation_uncertainty": uncertainty, + "gap": gap, + "span": span, + "distance_before": dminus, + "distance_after": dplus, + "fusion_weights_by_path": torch.stack(fusion_weight_paths, dim=0), + "null_weights_by_path": torch.stack(null_weight_paths, dim=0), + "time_pool_weights_by_path": torch.stack(time_pool_weight_paths, dim=0), + "low_rank_scale": torch.sigmoid(self.low_rank_eta_logit), + } diff --git a/submit/final/model/early_concat.py b/submit/final/model/early_concat.py new file mode 100644 index 0000000..debe0b4 --- /dev/null +++ b/submit/final/model/early_concat.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import torch +from torch import nn + + +class AlignedFusionModel(nn.Module): + """Early concatenation + BiGRU model for the supplied aligned sequence.""" + + def __init__( + self, + kind: str, + dims: tuple[int, int, int], + steps: int = 50, + hidden: int = 128, + dropout: float = 0.15, + ) -> None: + super().__init__() + if kind != "concat": + raise ValueError(f"only the selected EarlyConcat model is maintained; got: {kind}") + self.kind = kind + self.hidden = hidden + self.projections = nn.ModuleList( + nn.Sequential(nn.Linear(size, hidden), nn.GELU(), nn.LayerNorm(hidden)) + for size in dims + ) + self.position = nn.Parameter(torch.randn(1, steps, hidden) * 0.02) + self.modality = nn.Parameter(torch.randn(1, 1, 3, hidden) * 0.02) + self.dropout = nn.Dropout(dropout) + self.fusion = nn.Sequential( + nn.Linear(hidden * 3 + 3, hidden), nn.GELU(), nn.LayerNorm(hidden), nn.Dropout(dropout) + ) + self.temporal = nn.GRU( + input_size=hidden, + hidden_size=hidden // 2, + num_layers=1, + batch_first=True, + bidirectional=True, + ) + self.head = nn.Sequential(nn.Linear(hidden, hidden // 2), nn.GELU(), nn.Dropout(dropout)) + self.classifier = nn.Linear(hidden // 2, 3) + self.regressor = nn.Linear(hidden // 2, 1) + + def forward(self, xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], masks: torch.Tensor): + masks = masks.bool() + pos = self.position[:, :masks.shape[1]] + encoded = [] + for modality, (projection, x) in enumerate(zip(self.projections, xs)): + token = projection(x) + token = token + pos + self.modality[:, :, modality, :] + token = token * masks[:, :, modality, None] + encoded.append(token) + stack = torch.stack(encoded, dim=2) # B x T x M x D + availability = masks.to(stack.dtype) + fused = self.fusion(torch.cat((stack.flatten(2), availability), dim=-1)) + + temporal, _ = self.temporal(self.dropout(fused)) + time_weight = masks.any(dim=-1).to(temporal.dtype) + empty_time = time_weight.sum(dim=1, keepdim=True) <= 0 + if empty_time.any(): + time_weight[empty_time.squeeze(1), 0] = 1.0 + pooled = (temporal * time_weight[..., None]).sum(dim=1) + pooled = pooled / time_weight.sum(dim=1, keepdim=True).clamp_min(1.0) + hidden = self.head(pooled) + logits = self.classifier(hidden) + intensity = 3.0 * torch.tanh(self.regressor(hidden).squeeze(-1)) + return {"logits": logits, "intensity": intensity} diff --git a/submit/final/model/mofe.py b/submit/final/model/mofe.py new file mode 100644 index 0000000..615e986 --- /dev/null +++ b/submit/final/model/mofe.py @@ -0,0 +1,224 @@ +from __future__ import annotations + +from typing import Any + +import torch +import torch.nn.functional as F +from torch import nn + + +SUBSETS: dict[str, tuple[int, ...]] = { + "T": (0,), + "A": (1,), + "V": (2,), + "TA": (0, 1), + "TV": (0, 2), + "AV": (1, 2), + "TAV": (0, 1, 2), +} +EXPERT_NAMES = tuple(SUBSETS) +EXPERT_BITS = { + name: tuple(int(i in indices) for i in range(3)) + for name, indices in SUBSETS.items() +} + + +class MixtureOfFusionExperts(nn.Module): + """Seven-subset, hard-availability MoFE with the selected MLP router. + + Each modality has a private projection. Experts only receive the private + projections belonging to their subset. The weighted result is passed + through one shared temporal backbone and one shared prediction head. + """ + + def __init__( + self, + dims: tuple[int, int, int], + router: str = "mlp", + expert_names: tuple[str, ...] = EXPERT_NAMES, + availability_mode: str = "hard", + steps: int = 50, + latent_dim: int = 64, + hidden: int = 128, + dropout: float = 0.15, + ) -> None: + super().__init__() + if router != "mlp": + raise ValueError(f"only the selected MLP router is maintained; got: {router}") + if availability_mode != "hard": + raise ValueError(f"only hard availability masking is maintained; got: {availability_mode}") + if tuple(expert_names) != EXPERT_NAMES: + raise ValueError("the selected MoFE uses all seven modality-subset experts") + + self.dims = dims + self.router_kind = router + self.expert_names = tuple(expert_names) + self.availability_mode = availability_mode + self.steps = steps + self.latent_dim = latent_dim + self.hidden = hidden + + # These projections are private to each modality and are not tied. + self.private_projections = nn.ModuleList( + nn.Sequential(nn.Linear(size, latent_dim), nn.GELU()) for size in dims + ) + self.experts = nn.ModuleDict() + for name in self.expert_names: + n_modalities = len(SUBSETS[name]) + self.experts[name] = nn.Sequential( + nn.Linear(n_modalities * latent_dim, hidden), + nn.GELU(), + nn.Dropout(dropout), + nn.Linear(hidden, latent_dim), + nn.LayerNorm(latent_dim), + ) + + router_input_dim = 9 + self.router = nn.Sequential( + nn.Linear(router_input_dim, 16), + nn.GELU(), + nn.Linear(16, len(self.expert_names)), + ) + + # Shared early-fusion projection, BiGRU, and task heads. + self.all_missing_token = nn.Parameter(torch.zeros(1, 1, latent_dim)) + self.input_projection = nn.Sequential( + nn.Linear(latent_dim + 3, hidden), + nn.GELU(), + nn.LayerNorm(hidden), + nn.Dropout(dropout), + ) + self.dropout = nn.Dropout(dropout) + self.temporal = nn.GRU( + input_size=hidden, + hidden_size=hidden // 2, + num_layers=1, + batch_first=True, + bidirectional=True, + ) + self.head = nn.Sequential(nn.Linear(hidden, hidden // 2), nn.GELU(), nn.Dropout(dropout)) + self.classifier = nn.Linear(hidden // 2, 3) + self.regressor = nn.Linear(hidden // 2, 1) + + @staticmethod + def _availability(masks: torch.Tensor, names: tuple[str, ...]) -> torch.Tensor: + masks = masks.bool() + columns = [masks[..., list(SUBSETS[name])].all(dim=-1) for name in names] + return torch.stack(columns, dim=-1) + + def _router_features( + self, + private: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + masks: torch.Tensor, + ) -> torch.Tensor: + observed = masks.to(dtype=private[0].dtype) + magnitude = torch.stack( + [torch.sqrt(x.square().mean(dim=-1) + 1e-8) for x in private], dim=-1 + ) + local_ratio = F.avg_pool1d( + observed.transpose(1, 2), kernel_size=5, stride=1, padding=2, count_include_pad=False + ).transpose(1, 2) + return torch.cat((observed, torch.log1p(magnitude), local_ratio), dim=-1) + + def _route( + self, + router_features: torch.Tensor, + availability: torch.Tensor, + force_expert: str | None, + ) -> torch.Tensor: + scores = self.router(router_features) + scores = scores.masked_fill(~availability, -1e4) + weights = torch.softmax(scores, dim=-1) * availability.to(scores.dtype) + # In the full seven-expert model this is exactly the all-modalities- + # missing case. It also safely handles ablations with no eligible set. + has_expert = availability.any(dim=-1, keepdim=True) + weights = weights * has_expert.to(weights.dtype) + weights = weights / weights.sum(dim=-1, keepdim=True).clamp_min(1e-8) + + if force_expert is not None: + if force_expert not in self.expert_names: + raise ValueError(f"expert {force_expert} is not enabled in this model") + expert_idx = self.expert_names.index(force_expert) + forced = torch.zeros_like(weights) + forced[..., expert_idx] = 1.0 + # Force the requested expert where its modality subset is present; + # where it is unavailable, use the learned router over eligible + # experts instead of replacing observed information with zeros. + return torch.where(availability[..., expert_idx, None], forced, weights) + + return weights + + def forward( + self, + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + masks: torch.Tensor, + force_expert: str | None = None, + ) -> dict[str, Any]: + masks = masks.bool() + if masks.ndim != 3 or masks.shape[-1] != 3: + raise ValueError(f"masks must have shape B x T x 3, got {tuple(masks.shape)}") + if masks.shape[1] > self.steps: + raise ValueError(f"sequence has {masks.shape[1]} steps, model supports {self.steps}") + + private_values = [] + for modality, (projector, x) in enumerate(zip(self.private_projections, xs)): + projected = projector(x) + projected = projected * masks[..., modality, None].to(projected.dtype) + private_values.append(projected) + private = tuple(private_values) + router_features = self._router_features(private, masks) + availability = self._availability(masks, self.expert_names) + + local_expert_outputs = [] + for name in self.expert_names: + indices = SUBSETS[name] + expert_input = torch.cat([private[i] for i in indices], dim=-1) + local_expert_outputs.append(self.experts[name](expert_input)) + expert_stack = torch.stack(local_expert_outputs, dim=-2) + + alpha_local = self._route(router_features, availability, force_expert) + fused = (expert_stack * alpha_local[..., None]).sum(dim=-2) + has_expert = availability.any(dim=-1) + fused = torch.where( + has_expert[..., None], fused, self.all_missing_token.expand_as(fused) + ) + + # Restore a stable seven-column interface for saved diagnostics, + # including expert-set ablations. + alpha = masks.new_zeros((*masks.shape[:2], len(EXPERT_NAMES)), dtype=private[0].dtype) + expert_outputs = private[0].new_zeros((*masks.shape[:2], len(EXPERT_NAMES), self.latent_dim)) + for local_idx, name in enumerate(self.expert_names): + global_idx = EXPERT_NAMES.index(name) + alpha[..., global_idx] = alpha_local[..., local_idx] + expert_outputs[..., global_idx, :] = expert_stack[..., local_idx, :] + + fused_with_masks = torch.cat((fused, masks.to(fused.dtype)), dim=-1) + encoded = self.input_projection(fused_with_masks) + temporal, _ = self.temporal(self.dropout(encoded)) + time_weight = masks.any(dim=-1).to(temporal.dtype) + empty_time = time_weight.sum(dim=1, keepdim=True) <= 0 + if empty_time.any(): + time_weight[empty_time.squeeze(1), 0] = 1.0 + pooled = (temporal * time_weight[..., None]).sum(dim=1) + pooled = pooled / time_weight.sum(dim=1, keepdim=True).clamp_min(1.0) + hidden = self.head(pooled) + logits = self.classifier(hidden) + intensity = 3.0 * torch.tanh(self.regressor(hidden).squeeze(-1)) + + bits = torch.tensor( + [EXPERT_BITS[name] for name in EXPERT_NAMES], + dtype=alpha.dtype, + device=alpha.device, + ) + utility = torch.einsum("bte,em->btm", alpha, bits) + return { + "logits": logits, + "intensity": intensity, + "fused": fused, + "alpha": alpha, + "utility": utility, + "availability": availability, + "expert_outputs": expert_outputs, + "fallback": ~has_expert, + "router_features": router_features, + } diff --git a/submit/final/output/q1/features_v2/-3g5yACwYnA__13.npz b/submit/final/output/q1/features_v2/-3g5yACwYnA__13.npz new file mode 100644 index 0000000..8347cd0 Binary files /dev/null and b/submit/final/output/q1/features_v2/-3g5yACwYnA__13.npz differ diff --git a/submit/final/output/q1/features_v2/-3g5yACwYnA__2.npz b/submit/final/output/q1/features_v2/-3g5yACwYnA__2.npz new file mode 100644 index 0000000..c9c00cd Binary files /dev/null and b/submit/final/output/q1/features_v2/-3g5yACwYnA__2.npz differ diff --git a/submit/final/output/q1/features_v2/-3g5yACwYnA__3.npz b/submit/final/output/q1/features_v2/-3g5yACwYnA__3.npz new file mode 100644 index 0000000..07cd43c Binary files /dev/null and b/submit/final/output/q1/features_v2/-3g5yACwYnA__3.npz differ diff --git a/submit/final/output/q1/features_v2/-3g5yACwYnA__9.npz b/submit/final/output/q1/features_v2/-3g5yACwYnA__9.npz new file mode 100644 index 0000000..5254f84 Binary files /dev/null and b/submit/final/output/q1/features_v2/-3g5yACwYnA__9.npz differ diff --git a/submit/final/output/q1/features_v2/-3nNcZdcdvU__5.npz b/submit/final/output/q1/features_v2/-3nNcZdcdvU__5.npz new file mode 100644 index 0000000..6e55bd9 Binary files /dev/null and b/submit/final/output/q1/features_v2/-3nNcZdcdvU__5.npz differ diff --git a/submit/final/output/q1/features_v2/-571d8cVauQ__0.npz b/submit/final/output/q1/features_v2/-571d8cVauQ__0.npz new file mode 100644 index 0000000..506ead0 Binary files /dev/null and b/submit/final/output/q1/features_v2/-571d8cVauQ__0.npz differ diff --git a/submit/final/output/q1/features_v2/-571d8cVauQ__5.npz b/submit/final/output/q1/features_v2/-571d8cVauQ__5.npz new file mode 100644 index 0000000..6551a8b Binary files /dev/null and b/submit/final/output/q1/features_v2/-571d8cVauQ__5.npz differ diff --git a/submit/final/output/q1/features_v2/-6rXp3zJ3kc__8.npz b/submit/final/output/q1/features_v2/-6rXp3zJ3kc__8.npz new file mode 100644 index 0000000..442baa9 Binary files /dev/null and b/submit/final/output/q1/features_v2/-6rXp3zJ3kc__8.npz differ diff --git a/submit/final/output/q1/features_v2/-9y-fZ3swSY__0.npz b/submit/final/output/q1/features_v2/-9y-fZ3swSY__0.npz new file mode 100644 index 0000000..f87dc24 Binary files /dev/null and b/submit/final/output/q1/features_v2/-9y-fZ3swSY__0.npz differ diff --git a/submit/final/output/q1/features_v2/-9y-fZ3swSY__4.npz b/submit/final/output/q1/features_v2/-9y-fZ3swSY__4.npz new file mode 100644 index 0000000..e5227b5 Binary files /dev/null and b/submit/final/output/q1/features_v2/-9y-fZ3swSY__4.npz differ diff --git a/submit/final/output/q1/features_v2/-9y-fZ3swSY__8.npz b/submit/final/output/q1/features_v2/-9y-fZ3swSY__8.npz new file mode 100644 index 0000000..9dc5697 Binary files /dev/null and b/submit/final/output/q1/features_v2/-9y-fZ3swSY__8.npz differ diff --git a/submit/final/output/q1/features_v2/-AUZQgSxyPQ__2.npz b/submit/final/output/q1/features_v2/-AUZQgSxyPQ__2.npz new file mode 100644 index 0000000..3814384 Binary files /dev/null and b/submit/final/output/q1/features_v2/-AUZQgSxyPQ__2.npz differ diff --git a/submit/final/output/q1/features_v2/-HeZS2-Prhc__2.npz b/submit/final/output/q1/features_v2/-HeZS2-Prhc__2.npz new file mode 100644 index 0000000..7430ed0 Binary files /dev/null and b/submit/final/output/q1/features_v2/-HeZS2-Prhc__2.npz differ diff --git a/submit/final/output/q1/features_v2/-HwX2H8Z4hY__2.npz b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__2.npz new file mode 100644 index 0000000..677f7ad Binary files /dev/null and b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__2.npz differ diff --git a/submit/final/output/q1/features_v2/-HwX2H8Z4hY__5.npz b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__5.npz new file mode 100644 index 0000000..bab0d25 Binary files /dev/null and b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__5.npz differ diff --git a/submit/final/output/q1/features_v2/-HwX2H8Z4hY__6.npz b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__6.npz new file mode 100644 index 0000000..fa8c052 Binary files /dev/null and b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__6.npz differ diff --git a/submit/final/output/q1/features_v2/-HwX2H8Z4hY__9.npz b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__9.npz new file mode 100644 index 0000000..39a76f9 Binary files /dev/null and b/submit/final/output/q1/features_v2/-HwX2H8Z4hY__9.npz differ diff --git a/submit/final/output/q1/features_v2/-I_e4mIh0yE__1.npz b/submit/final/output/q1/features_v2/-I_e4mIh0yE__1.npz new file mode 100644 index 0000000..fbef855 Binary files /dev/null and b/submit/final/output/q1/features_v2/-I_e4mIh0yE__1.npz differ diff --git a/submit/final/output/q1/features_v2/-I_e4mIh0yE__3.npz b/submit/final/output/q1/features_v2/-I_e4mIh0yE__3.npz new file mode 100644 index 0000000..3467ad5 Binary files /dev/null and b/submit/final/output/q1/features_v2/-I_e4mIh0yE__3.npz differ diff --git a/submit/final/output/q1/features_v2/-MeTTeMJBNc__0.npz b/submit/final/output/q1/features_v2/-MeTTeMJBNc__0.npz new file mode 100644 index 0000000..f4ca22a Binary files /dev/null and b/submit/final/output/q1/features_v2/-MeTTeMJBNc__0.npz differ diff --git a/submit/final/output/q1/features_v2/-MeTTeMJBNc__13.npz b/submit/final/output/q1/features_v2/-MeTTeMJBNc__13.npz new file mode 100644 index 0000000..ca0c067 Binary files /dev/null and b/submit/final/output/q1/features_v2/-MeTTeMJBNc__13.npz differ diff --git a/submit/final/output/q1/features_v2/-MeTTeMJBNc__7.npz b/submit/final/output/q1/features_v2/-MeTTeMJBNc__7.npz new file mode 100644 index 0000000..881ea4f Binary files /dev/null and b/submit/final/output/q1/features_v2/-MeTTeMJBNc__7.npz differ diff --git a/submit/final/output/q1/features_v2/-NFrJFQijFE__1.npz b/submit/final/output/q1/features_v2/-NFrJFQijFE__1.npz new file mode 100644 index 0000000..725d366 Binary files /dev/null and b/submit/final/output/q1/features_v2/-NFrJFQijFE__1.npz differ diff --git a/submit/final/output/q1/features_v2/-NFrJFQijFE__2.npz b/submit/final/output/q1/features_v2/-NFrJFQijFE__2.npz new file mode 100644 index 0000000..b575528 Binary files /dev/null and b/submit/final/output/q1/features_v2/-NFrJFQijFE__2.npz differ diff --git a/submit/final/output/q1/features_v2/-RfYyzHpjk4__11.npz b/submit/final/output/q1/features_v2/-RfYyzHpjk4__11.npz new file mode 100644 index 0000000..8e8b680 Binary files /dev/null and b/submit/final/output/q1/features_v2/-RfYyzHpjk4__11.npz differ diff --git a/submit/final/output/q1/features_v2/-RfYyzHpjk4__2.npz b/submit/final/output/q1/features_v2/-RfYyzHpjk4__2.npz new file mode 100644 index 0000000..7c0be19 Binary files /dev/null and b/submit/final/output/q1/features_v2/-RfYyzHpjk4__2.npz differ diff --git a/submit/final/output/q1/features_v2/-RfYyzHpjk4__8.npz b/submit/final/output/q1/features_v2/-RfYyzHpjk4__8.npz new file mode 100644 index 0000000..33200fc Binary files /dev/null and b/submit/final/output/q1/features_v2/-RfYyzHpjk4__8.npz differ diff --git a/submit/final/output/q1/features_v2/-THoVjtIkeU__12.npz b/submit/final/output/q1/features_v2/-THoVjtIkeU__12.npz new file mode 100644 index 0000000..6adc9cd Binary files /dev/null and b/submit/final/output/q1/features_v2/-THoVjtIkeU__12.npz differ diff --git a/submit/final/output/q1/features_v2/-THoVjtIkeU__2.npz b/submit/final/output/q1/features_v2/-THoVjtIkeU__2.npz new file mode 100644 index 0000000..9a0c3dd Binary files /dev/null and b/submit/final/output/q1/features_v2/-THoVjtIkeU__2.npz differ diff --git a/submit/final/output/q1/features_v2/-THoVjtIkeU__6.npz b/submit/final/output/q1/features_v2/-THoVjtIkeU__6.npz new file mode 100644 index 0000000..7b7b89e Binary files /dev/null and b/submit/final/output/q1/features_v2/-THoVjtIkeU__6.npz differ diff --git a/submit/final/output/q1/features_v2/-UUCSKoHeMA__0.npz b/submit/final/output/q1/features_v2/-UUCSKoHeMA__0.npz new file mode 100644 index 0000000..5e56085 Binary files /dev/null and b/submit/final/output/q1/features_v2/-UUCSKoHeMA__0.npz differ diff --git a/submit/final/output/q1/features_v2/-UacrmKiTn4__10.npz b/submit/final/output/q1/features_v2/-UacrmKiTn4__10.npz new file mode 100644 index 0000000..0719f9e Binary files /dev/null and b/submit/final/output/q1/features_v2/-UacrmKiTn4__10.npz differ diff --git a/submit/final/output/q1/features_v2/-UacrmKiTn4__4.npz b/submit/final/output/q1/features_v2/-UacrmKiTn4__4.npz new file mode 100644 index 0000000..c302e70 Binary files /dev/null and b/submit/final/output/q1/features_v2/-UacrmKiTn4__4.npz differ diff --git a/submit/final/output/q1/features_v2/-UuX1xuaiiE__0.npz b/submit/final/output/q1/features_v2/-UuX1xuaiiE__0.npz new file mode 100644 index 0000000..0c8a822 Binary files /dev/null and b/submit/final/output/q1/features_v2/-UuX1xuaiiE__0.npz differ diff --git a/submit/final/output/q1/features_v2/-UuX1xuaiiE__1.npz b/submit/final/output/q1/features_v2/-UuX1xuaiiE__1.npz new file mode 100644 index 0000000..f929596 Binary files /dev/null and b/submit/final/output/q1/features_v2/-UuX1xuaiiE__1.npz differ diff --git a/submit/final/output/q1/features_v2/-UuX1xuaiiE__3.npz b/submit/final/output/q1/features_v2/-UuX1xuaiiE__3.npz new file mode 100644 index 0000000..c14a2e2 Binary files /dev/null and b/submit/final/output/q1/features_v2/-UuX1xuaiiE__3.npz differ diff --git a/submit/final/output/q1/features_v2/-UuX1xuaiiE__6.npz b/submit/final/output/q1/features_v2/-UuX1xuaiiE__6.npz new file mode 100644 index 0000000..b4bb48e Binary files /dev/null and b/submit/final/output/q1/features_v2/-UuX1xuaiiE__6.npz differ diff --git a/submit/final/output/q1/features_v2/-a55Q6RWvTA__3.npz b/submit/final/output/q1/features_v2/-a55Q6RWvTA__3.npz new file mode 100644 index 0000000..886f69f Binary files /dev/null and b/submit/final/output/q1/features_v2/-a55Q6RWvTA__3.npz differ diff --git a/submit/final/output/q1/features_v2/-aNfi7CP8vM__7.npz b/submit/final/output/q1/features_v2/-aNfi7CP8vM__7.npz new file mode 100644 index 0000000..d1502fa Binary files /dev/null and b/submit/final/output/q1/features_v2/-aNfi7CP8vM__7.npz differ diff --git a/submit/final/output/q1/features_v2/-aqamKhZ1Ec__0.npz b/submit/final/output/q1/features_v2/-aqamKhZ1Ec__0.npz new file mode 100644 index 0000000..347de31 Binary files /dev/null and b/submit/final/output/q1/features_v2/-aqamKhZ1Ec__0.npz differ diff --git a/submit/final/output/q1/features_v2/-dxfTGcXJoc__0.npz b/submit/final/output/q1/features_v2/-dxfTGcXJoc__0.npz new file mode 100644 index 0000000..08d6fec Binary files /dev/null and b/submit/final/output/q1/features_v2/-dxfTGcXJoc__0.npz differ diff --git a/submit/final/output/q1/features_v2/-dxfTGcXJoc__1.npz b/submit/final/output/q1/features_v2/-dxfTGcXJoc__1.npz new file mode 100644 index 0000000..10322d6 Binary files /dev/null and b/submit/final/output/q1/features_v2/-dxfTGcXJoc__1.npz differ diff --git a/submit/final/output/q1/features_v2/-dxfTGcXJoc__2.npz b/submit/final/output/q1/features_v2/-dxfTGcXJoc__2.npz new file mode 100644 index 0000000..5aa99b3 Binary files /dev/null and b/submit/final/output/q1/features_v2/-dxfTGcXJoc__2.npz differ diff --git a/submit/final/output/q1/features_v2/-dxfTGcXJoc__6.npz b/submit/final/output/q1/features_v2/-dxfTGcXJoc__6.npz new file mode 100644 index 0000000..b665039 Binary files /dev/null and b/submit/final/output/q1/features_v2/-dxfTGcXJoc__6.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__1.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__1.npz new file mode 100644 index 0000000..3eb2753 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__1.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__13.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__13.npz new file mode 100644 index 0000000..81f8729 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__13.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__16.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__16.npz new file mode 100644 index 0000000..b338f97 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__16.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__17.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__17.npz new file mode 100644 index 0000000..a47cf16 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__17.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__18.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__18.npz new file mode 100644 index 0000000..67b1865 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__18.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__20.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__20.npz new file mode 100644 index 0000000..0bb3e07 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__20.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__26.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__26.npz new file mode 100644 index 0000000..7892704 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__26.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__6.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__6.npz new file mode 100644 index 0000000..8b56272 Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__6.npz differ diff --git a/submit/final/output/q1/features_v2/-egA8-b7-3M__9.npz b/submit/final/output/q1/features_v2/-egA8-b7-3M__9.npz new file mode 100644 index 0000000..3de297e Binary files /dev/null and b/submit/final/output/q1/features_v2/-egA8-b7-3M__9.npz differ diff --git a/submit/final/output/q1/features_v2/-hnBHBN8p5A__6.npz b/submit/final/output/q1/features_v2/-hnBHBN8p5A__6.npz new file mode 100644 index 0000000..e0aaf04 Binary files /dev/null and b/submit/final/output/q1/features_v2/-hnBHBN8p5A__6.npz differ diff --git a/submit/final/output/q1/features_v2/-hnBHBN8p5A__7.npz b/submit/final/output/q1/features_v2/-hnBHBN8p5A__7.npz new file mode 100644 index 0000000..85f91d2 Binary files /dev/null and b/submit/final/output/q1/features_v2/-hnBHBN8p5A__7.npz differ diff --git a/submit/final/output/q1/features_v2/-iRBcNs9oI8__3.npz b/submit/final/output/q1/features_v2/-iRBcNs9oI8__3.npz new file mode 100644 index 0000000..8443d28 Binary files /dev/null and b/submit/final/output/q1/features_v2/-iRBcNs9oI8__3.npz differ diff --git a/submit/final/output/q1/features_v2/-iRBcNs9oI8__6.npz b/submit/final/output/q1/features_v2/-iRBcNs9oI8__6.npz new file mode 100644 index 0000000..c22ced4 Binary files /dev/null and b/submit/final/output/q1/features_v2/-iRBcNs9oI8__6.npz differ diff --git a/submit/final/output/q1/features_v2/-iRBcNs9oI8__7.npz b/submit/final/output/q1/features_v2/-iRBcNs9oI8__7.npz new file mode 100644 index 0000000..c9baa38 Binary files /dev/null and b/submit/final/output/q1/features_v2/-iRBcNs9oI8__7.npz differ diff --git a/submit/final/output/q1/features_v2/-iRBcNs9oI8__8.npz b/submit/final/output/q1/features_v2/-iRBcNs9oI8__8.npz new file mode 100644 index 0000000..ed6a047 Binary files /dev/null and b/submit/final/output/q1/features_v2/-iRBcNs9oI8__8.npz differ diff --git a/submit/final/output/q1/features_v2/-iRBcNs9oI8__9.npz b/submit/final/output/q1/features_v2/-iRBcNs9oI8__9.npz new file mode 100644 index 0000000..698b9f1 Binary files /dev/null and b/submit/final/output/q1/features_v2/-iRBcNs9oI8__9.npz differ diff --git a/submit/final/output/q1/features_v2/-lzEya4AM_4__5.npz b/submit/final/output/q1/features_v2/-lzEya4AM_4__5.npz new file mode 100644 index 0000000..857224a Binary files /dev/null and b/submit/final/output/q1/features_v2/-lzEya4AM_4__5.npz differ diff --git a/submit/final/output/q1/features_v2/-lzEya4AM_4__6.npz b/submit/final/output/q1/features_v2/-lzEya4AM_4__6.npz new file mode 100644 index 0000000..3d35f91 Binary files /dev/null and b/submit/final/output/q1/features_v2/-lzEya4AM_4__6.npz differ diff --git a/submit/final/output/q1/features_v2/-mJ2ud6oKI8__1.npz b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__1.npz new file mode 100644 index 0000000..13837b8 Binary files /dev/null and b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__1.npz differ diff --git a/submit/final/output/q1/features_v2/-mJ2ud6oKI8__2.npz b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__2.npz new file mode 100644 index 0000000..bd2aa02 Binary files /dev/null and b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__2.npz differ diff --git a/submit/final/output/q1/features_v2/-mJ2ud6oKI8__6.npz b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__6.npz new file mode 100644 index 0000000..9550199 Binary files /dev/null and b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__6.npz differ diff --git a/submit/final/output/q1/features_v2/-mJ2ud6oKI8__8.npz b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__8.npz new file mode 100644 index 0000000..5f7a192 Binary files /dev/null and b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__8.npz differ diff --git a/submit/final/output/q1/features_v2/-mJ2ud6oKI8__9.npz b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__9.npz new file mode 100644 index 0000000..34a4e5a Binary files /dev/null and b/submit/final/output/q1/features_v2/-mJ2ud6oKI8__9.npz differ diff --git a/submit/final/output/q1/features_v2/-mqbVkbCndg__0.npz b/submit/final/output/q1/features_v2/-mqbVkbCndg__0.npz new file mode 100644 index 0000000..71b65eb Binary files /dev/null and b/submit/final/output/q1/features_v2/-mqbVkbCndg__0.npz differ diff --git a/submit/final/output/q1/features_v2/-qDkUB0GgYY__6.npz b/submit/final/output/q1/features_v2/-qDkUB0GgYY__6.npz new file mode 100644 index 0000000..0782e28 Binary files /dev/null and b/submit/final/output/q1/features_v2/-qDkUB0GgYY__6.npz differ diff --git a/submit/final/output/q1/features_v2/-ri04Z7vwnc__0.npz b/submit/final/output/q1/features_v2/-ri04Z7vwnc__0.npz new file mode 100644 index 0000000..929fdd9 Binary files /dev/null and b/submit/final/output/q1/features_v2/-ri04Z7vwnc__0.npz differ diff --git a/submit/final/output/q1/features_v2/-ri04Z7vwnc__2.npz b/submit/final/output/q1/features_v2/-ri04Z7vwnc__2.npz new file mode 100644 index 0000000..678f0e7 Binary files /dev/null and b/submit/final/output/q1/features_v2/-ri04Z7vwnc__2.npz differ diff --git a/submit/final/output/q1/features_v2/-ri04Z7vwnc__5.npz b/submit/final/output/q1/features_v2/-ri04Z7vwnc__5.npz new file mode 100644 index 0000000..baa8822 Binary files /dev/null and b/submit/final/output/q1/features_v2/-ri04Z7vwnc__5.npz differ diff --git a/submit/final/output/q1/features_v2/-s9qJ7ATP7w__0.npz b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__0.npz new file mode 100644 index 0000000..653699a Binary files /dev/null and b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__0.npz differ diff --git a/submit/final/output/q1/features_v2/-s9qJ7ATP7w__1.npz b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__1.npz new file mode 100644 index 0000000..0de50f3 Binary files /dev/null and b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__1.npz differ diff --git a/submit/final/output/q1/features_v2/-s9qJ7ATP7w__4.npz b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__4.npz new file mode 100644 index 0000000..b7e686d Binary files /dev/null and b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__4.npz differ diff --git a/submit/final/output/q1/features_v2/-s9qJ7ATP7w__5.npz b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__5.npz new file mode 100644 index 0000000..b2d98f5 Binary files /dev/null and b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__5.npz differ diff --git a/submit/final/output/q1/features_v2/-s9qJ7ATP7w__6.npz b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__6.npz new file mode 100644 index 0000000..f5358d8 Binary files /dev/null and b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__6.npz differ diff --git a/submit/final/output/q1/features_v2/-s9qJ7ATP7w__7.npz b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__7.npz new file mode 100644 index 0000000..6f207e2 Binary files /dev/null and b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__7.npz differ diff --git a/submit/final/output/q1/features_v2/-s9qJ7ATP7w__8.npz b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__8.npz new file mode 100644 index 0000000..27f5048 Binary files /dev/null and b/submit/final/output/q1/features_v2/-s9qJ7ATP7w__8.npz differ diff --git a/submit/final/output/q1/features_v2/-t217m2on-s__2.npz b/submit/final/output/q1/features_v2/-t217m2on-s__2.npz new file mode 100644 index 0000000..4d61d46 Binary files /dev/null and b/submit/final/output/q1/features_v2/-t217m2on-s__2.npz differ diff --git a/submit/final/output/q1/features_v2/-t217m2on-s__7.npz b/submit/final/output/q1/features_v2/-t217m2on-s__7.npz new file mode 100644 index 0000000..9ad977a Binary files /dev/null and b/submit/final/output/q1/features_v2/-t217m2on-s__7.npz differ diff --git a/submit/final/output/q1/features_v2/-tANM6ETl_M__3.npz b/submit/final/output/q1/features_v2/-tANM6ETl_M__3.npz new file mode 100644 index 0000000..75d401c Binary files /dev/null and b/submit/final/output/q1/features_v2/-tANM6ETl_M__3.npz differ diff --git a/submit/final/output/q1/features_v2/-tPCytz4rww__10.npz b/submit/final/output/q1/features_v2/-tPCytz4rww__10.npz new file mode 100644 index 0000000..4912786 Binary files /dev/null and b/submit/final/output/q1/features_v2/-tPCytz4rww__10.npz differ diff --git a/submit/final/output/q1/features_v2/-tPCytz4rww__11.npz b/submit/final/output/q1/features_v2/-tPCytz4rww__11.npz new file mode 100644 index 0000000..530d0e2 Binary files /dev/null and b/submit/final/output/q1/features_v2/-tPCytz4rww__11.npz differ diff --git a/submit/final/output/q1/features_v2/-tPCytz4rww__12.npz b/submit/final/output/q1/features_v2/-tPCytz4rww__12.npz new file mode 100644 index 0000000..ca10460 Binary files /dev/null and b/submit/final/output/q1/features_v2/-tPCytz4rww__12.npz differ diff --git a/submit/final/output/q1/features_v2/-tPCytz4rww__16.npz b/submit/final/output/q1/features_v2/-tPCytz4rww__16.npz new file mode 100644 index 0000000..8f579c8 Binary files /dev/null and b/submit/final/output/q1/features_v2/-tPCytz4rww__16.npz differ diff --git a/submit/final/output/q1/features_v2/-tPCytz4rww__18.npz b/submit/final/output/q1/features_v2/-tPCytz4rww__18.npz new file mode 100644 index 0000000..8a040d7 Binary files /dev/null and b/submit/final/output/q1/features_v2/-tPCytz4rww__18.npz differ diff --git a/submit/final/output/q1/features_v2/-uywlfIYOS8__4.npz b/submit/final/output/q1/features_v2/-uywlfIYOS8__4.npz new file mode 100644 index 0000000..cde8f90 Binary files /dev/null and b/submit/final/output/q1/features_v2/-uywlfIYOS8__4.npz differ diff --git a/submit/final/output/q1/features_v2/-vxjVxOeScU__4.npz b/submit/final/output/q1/features_v2/-vxjVxOeScU__4.npz new file mode 100644 index 0000000..0889aea Binary files /dev/null and b/submit/final/output/q1/features_v2/-vxjVxOeScU__4.npz differ diff --git a/submit/final/output/q1/features_v2/-wMB_hJL-3o__7.npz b/submit/final/output/q1/features_v2/-wMB_hJL-3o__7.npz new file mode 100644 index 0000000..9ff10cb Binary files /dev/null and b/submit/final/output/q1/features_v2/-wMB_hJL-3o__7.npz differ diff --git a/submit/final/output/q1/features_v2/-wny0OAz3g8__0.npz b/submit/final/output/q1/features_v2/-wny0OAz3g8__0.npz new file mode 100644 index 0000000..29b6a31 Binary files /dev/null and b/submit/final/output/q1/features_v2/-wny0OAz3g8__0.npz differ diff --git a/submit/final/output/q1/features_v2/-wny0OAz3g8__1.npz b/submit/final/output/q1/features_v2/-wny0OAz3g8__1.npz new file mode 100644 index 0000000..ddb9509 Binary files /dev/null and b/submit/final/output/q1/features_v2/-wny0OAz3g8__1.npz differ diff --git a/submit/final/output/q1/features_v2/-wny0OAz3g8__2.npz b/submit/final/output/q1/features_v2/-wny0OAz3g8__2.npz new file mode 100644 index 0000000..52f959b Binary files /dev/null and b/submit/final/output/q1/features_v2/-wny0OAz3g8__2.npz differ diff --git a/submit/final/output/q1/features_v2/-wny0OAz3g8__3.npz b/submit/final/output/q1/features_v2/-wny0OAz3g8__3.npz new file mode 100644 index 0000000..4ecd6a3 Binary files /dev/null and b/submit/final/output/q1/features_v2/-wny0OAz3g8__3.npz differ diff --git a/submit/final/output/q1/features_v2/-wny0OAz3g8__5.npz b/submit/final/output/q1/features_v2/-wny0OAz3g8__5.npz new file mode 100644 index 0000000..0f37c95 Binary files /dev/null and b/submit/final/output/q1/features_v2/-wny0OAz3g8__5.npz differ diff --git a/submit/final/output/q1/features_v2/-wny0OAz3g8__7.npz b/submit/final/output/q1/features_v2/-wny0OAz3g8__7.npz new file mode 100644 index 0000000..3263034 Binary files /dev/null and b/submit/final/output/q1/features_v2/-wny0OAz3g8__7.npz differ diff --git a/submit/final/output/q1/features_v2/-wny0OAz3g8__9.npz b/submit/final/output/q1/features_v2/-wny0OAz3g8__9.npz new file mode 100644 index 0000000..aa04812 Binary files /dev/null and b/submit/final/output/q1/features_v2/-wny0OAz3g8__9.npz differ diff --git a/submit/final/output/q1/features_v2/-yRb-Jum7EQ__1.npz b/submit/final/output/q1/features_v2/-yRb-Jum7EQ__1.npz new file mode 100644 index 0000000..aaa2f11 Binary files /dev/null and b/submit/final/output/q1/features_v2/-yRb-Jum7EQ__1.npz differ diff --git a/submit/final/output/q1/features_v2/-yRb-Jum7EQ__5.npz b/submit/final/output/q1/features_v2/-yRb-Jum7EQ__5.npz new file mode 100644 index 0000000..4f34029 Binary files /dev/null and b/submit/final/output/q1/features_v2/-yRb-Jum7EQ__5.npz differ diff --git a/submit/final/output/q1/features_v2/-yRb-Jum7EQ__6.npz b/submit/final/output/q1/features_v2/-yRb-Jum7EQ__6.npz new file mode 100644 index 0000000..d3dc2c2 Binary files /dev/null and b/submit/final/output/q1/features_v2/-yRb-Jum7EQ__6.npz differ diff --git a/submit/final/output/q1/features_v2/feature_manifest.json b/submit/final/output/q1/features_v2/feature_manifest.json new file mode 100644 index 0000000..77a5acb --- /dev/null +++ b/submit/final/output/q1/features_v2/feature_manifest.json @@ -0,0 +1,59 @@ +{ + "config": { + "schema": "q1-v2-source-first-2026-09", + "source_cache_schema": "q1-b0b4-v4-openface", + "common_step_s": 0.1, + "multi_context_windows_s": [ + 0.1, + 0.3, + 0.7 + ], + "relative_progress_bins": 50, + "text_encoder": "google-bert/bert-base-uncased; frozen last-four-layer mean per whitespace word; window=510, stride=384", + "alignment": "fixed-transcript CTC monotone state graph; hard Viterbi path plus stored forward-backward occupancy; scores uncalibrated", + "audio": "74-D: 40 log-Mel + 13 MFCC + 13 two-sided local-linear delta MFCC + [log-energy, log-F0, librosa.pyin voiced probability, spectral centroid, bandwidth, flux, zero-crossing rate, HNR dB]; 16 kHz, 400-sample window, 160-sample hop, FFT 512; HNR autocorrelation uses n=1024 at detected F0; preserve per-field validity; librosa.pyin warns cycle support is short at fmin=80 Hz for a 25 ms frame", + "vision": "OpenFace 2.2.0 native-frame output: 17 AU intensities, Pose6, Gaze6, Geometry6; confidence threshold 0.8; actual frame PTS and indices retained", + "quality": "B0 uses raw CTC word-path score in [0,1]; unavailable quality falls back to q*=1 and is flagged; audio/vision retain explicit unknown or detector-confidence states", + "content_probe": "disabled; physical time/query maps remain separate from content similarity", + "time": "left-closed/right-open seconds relative to video stream origin; source indices retained", + "branch_states": { + "sec": "primary B0 view and materialized", + "B1_vision_geometry": "geometry-aware 0.1 s view materialized", + "word": "materialized", + "phase50": "materialized", + "multi": "derived from native rows on demand by q1_io", + "posterior": "native CTC posterior stored; sec projection available through q1_io", + "dynamics": "materialized if continuous support is sufficient", + "b4_speech": "source-cache-backed on demand through q1_io to stay within the 50 MiB feature-package cap", + "query_word": "physical H_time matrices materialized", + "content_probe": "disabled" + } + }, + "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617", + "source_run_manifest": "source_cache_manifest.json", + "source_cache_schema": "q1-b0b4-v4-openface", + "source_label_sha256": "827334f782b1f242c84ad944a657bc7f7c63643f71ae9c977811a3eecdd3ab33", + "python": "3.14.7 (main, Aug 10 2026, 00:00:00) [GCC 16.1.1 20260515 (Red Hat 16.1.1-2)]", + "packages": { + "torch": "2.14.0+cu130", + "transformers": "5.17.0", + "numpy": "2.5.3", + "scipy": "1.18.1", + "scikit-learn": "1.9.1", + "opencv-python-headless": "5.0.0.93", + "librosa": "1.0.0", + "openpyxl": "3.1.5", + "matplotlib": "3.11.2" + }, + "models": { + "text_id": "google-bert/bert-base-uncased", + "text_revision": "86b5e0934494bd15c9632b12f734a8a67f723594", + "speech_id": "facebook/wav2vec2-base-960h", + "speech_revision": "22aad52d435eb6dbaf354bdad9b0da84ce7d6156", + "speech_stride_samples": 320, + "speech_receptive_field_samples": 400 + }, + "samples": 100, + "modalities": 300, + "total_bytes": 44321567 +} diff --git a/submit/final/output/q1/features_v2/manifest_q1.jsonl b/submit/final/output/q1/features_v2/manifest_q1.jsonl new file mode 100644 index 0000000..008bdd3 --- /dev/null +++ b/submit/final/output/q1/features_v2/manifest_q1.jsonl @@ -0,0 +1,100 @@ +{"sample_id": "-3g5yACwYnA/13", "video_id": "-3g5yACwYnA", "clip_id": "13", "feature_path": "features_v2/-3g5yACwYnA__13.npz", "source_video_sha256": "aeb47627f59dce3d0a85a44ef35e4a3bc18211498e98c8c11cf60269646df24f", "feature_sha256": "8ff79652816c4749ebfb19c95fb1f4826e27e19e850db23a289144985bcacb51", "duration_s": 5.5139970779418945, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-3g5yACwYnA/3", "video_id": "-3g5yACwYnA", "clip_id": "3", "feature_path": "features_v2/-3g5yACwYnA__3.npz", "source_video_sha256": "eff8cfefba2425155f2b72656829b34b23be8bfe1dcbece384154f56818aa26d", "feature_sha256": "e3756cf59da4cc1f56b316b9af66e1afbd7f76d45ed74296ee5e239e078bce61", "duration_s": 14.388997077941895, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-3g5yACwYnA/2", "video_id": "-3g5yACwYnA", "clip_id": "2", "feature_path": "features_v2/-3g5yACwYnA__2.npz", "source_video_sha256": "0619c01f137d017c15c058176f18a575919189c7e81ebd2bfb993afdb86f3de7", "feature_sha256": "e84dc34a549d0d9ed9200d889c8c4a3c9e3af2cf69db297939794cee052f2ab8", "duration_s": 9.394009590148926, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-3g5yACwYnA/9", "video_id": "-3g5yACwYnA", "clip_id": "9", "feature_path": "features_v2/-3g5yACwYnA__9.npz", "source_video_sha256": "b962573b12ed1f06d5533f6427c80dce2bae3a99dd332f6d3fcb32c579f3f016", "feature_sha256": "585ffb341161b798fb8213915eee94593519cf50a6510dad690d8386d2ca5f40", "duration_s": 8.816991806030273, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-3nNcZdcdvU/5", "video_id": "-3nNcZdcdvU", "clip_id": "5", "feature_path": "features_v2/-3nNcZdcdvU__5.npz", "source_video_sha256": "7ea53ad502b77be7d2ee4494dc23a9478c897792203cf887d546e83d584c1728", "feature_sha256": "a69c1bca94b0fa15c69c31eb4909874a08ffad213fb10b5d27cf3ba1cabfa13f", "duration_s": 7.867969036102295, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-HwX2H8Z4hY/2", "video_id": "-HwX2H8Z4hY", "clip_id": "2", "feature_path": "features_v2/-HwX2H8Z4hY__2.npz", "source_video_sha256": "920a1052c09ebd1eedba6dd1ccfecd80f56f6f0fdcdd1f6dd32b0d90bcb89358", "feature_sha256": "bce95b4eaa69f6b35684b191461419ecc2083ee944ced41807e46eba8d3131c5", "duration_s": 3.9820311069488525, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-HwX2H8Z4hY/5", "video_id": "-HwX2H8Z4hY", "clip_id": "5", "feature_path": "features_v2/-HwX2H8Z4hY__5.npz", "source_video_sha256": "8d61b7f5840a8bd403d9bc6c3cd49029ffc4e304cf9c7834735c1a6bb5fd369d", "feature_sha256": "843601e008fa1ddcf4dc530837017e5fbccfc7f6aeaa3aff1b62a336668a0b4e", "duration_s": 5.6529951095581055, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-HwX2H8Z4hY/6", "video_id": "-HwX2H8Z4hY", "clip_id": "6", "feature_path": "features_v2/-HwX2H8Z4hY__6.npz", "source_video_sha256": "696576dcbd0dc42cb678fa40d8a0f0f419a3072bfa7662d653756ab29bba4c46", "feature_sha256": "e0f9f2edd105ce67f8269fb3a41603af01002047e25a450cf8dda1b4234b2141", "duration_s": 3.3580079078674316, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-HwX2H8Z4hY/9", "video_id": "-HwX2H8Z4hY", "clip_id": "9", "feature_path": "features_v2/-HwX2H8Z4hY__9.npz", "source_video_sha256": "90c18e627f27a12c14e21ba24c79edf3008762b4ac26874b490ad3f888381702", "feature_sha256": "8248b6823c7a39a16c44389a7ef71fa206492ce35ccea39ea62621c3bcce1721", "duration_s": 7.733983993530273, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-NFrJFQijFE/1", "video_id": "-NFrJFQijFE", "clip_id": "1", "feature_path": "features_v2/-NFrJFQijFE__1.npz", "source_video_sha256": "d8fcf16c6eb51c56947ec3d0549c6f3bc7a0cf1fc5f5d89e8b38d4249a08db4f", "feature_sha256": "1b5b76764b7a6ce5bd68eca4dc24347e3d0e800bc2abe3c6af28cca881f4f498", "duration_s": 5.745999813079834, "status": "partial", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-NFrJFQijFE/2", "video_id": "-NFrJFQijFE", "clip_id": "2", "feature_path": "features_v2/-NFrJFQijFE__2.npz", "source_video_sha256": "a54a5c144a64f5abd26b19aafa6cea8142ee08e9385fa508fa70c934f6b616c1", "feature_sha256": "3805d0f3ea3c570accc297ebea6af7e0cfcc6f0f100593a6b06b34b5563a237a", "duration_s": 6.855999946594238, "status": "partial", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-THoVjtIkeU/12", "video_id": "-THoVjtIkeU", "clip_id": "12", "feature_path": "features_v2/-THoVjtIkeU__12.npz", "source_video_sha256": "e2f148f4f2e74724dc13b3ce870a0ce6a06740cfc224f5a9b3d0a870b196e7d4", "feature_sha256": "7830ce91514263b5cff4c305da3cf42cb86eef37158aad86f625d79f7268ac07", "duration_s": 14.896029472351074, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-THoVjtIkeU/2", "video_id": "-THoVjtIkeU", "clip_id": "2", "feature_path": "features_v2/-THoVjtIkeU__2.npz", "source_video_sha256": "35a6c2464ffd68dd96411edb5dc2763f0efb59f264c1d8b737e616fa6b0d1d5b", "feature_sha256": "96d2c188b9ddaa891af1dfce83e12c5ed49abc0e85d96b802a8d28ebdec4dc66", "duration_s": 4.2919921875, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-THoVjtIkeU/6", "video_id": "-THoVjtIkeU", "clip_id": "6", "feature_path": "features_v2/-THoVjtIkeU__6.npz", "source_video_sha256": "efaa55ee6394032c867046690a92c29edfd825005f0a090cca5ccb763b227f64", "feature_sha256": "8957ebe6c0583a0dce593ce8cc42db829fdd1be8e112bcf08d99b083f5a3ac8a", "duration_s": 8.097004890441895, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-UuX1xuaiiE/1", "video_id": "-UuX1xuaiiE", "clip_id": "1", "feature_path": "features_v2/-UuX1xuaiiE__1.npz", "source_video_sha256": "d5bbda38fcec15817d2b87bab5dcc559d6d425f7d28a82e6c32d13f14b48650c", "feature_sha256": "aecd48aa4a55db3bb21c7fadbb5b301f00b4ec235bc67a47dc5141bb32a7be9a", "duration_s": 10.350000381469727, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-UuX1xuaiiE/0", "video_id": "-UuX1xuaiiE", "clip_id": "0", "feature_path": "features_v2/-UuX1xuaiiE__0.npz", "source_video_sha256": "f578b305d53bc152702f5e771de0fcd12d1e9aaefc5cefce3d2082a417aab7c8", "feature_sha256": "2e24c45d36ef3c1925706d397f7c69deab53f7838a58bb66d94b5c03d09f1258", "duration_s": 3.6333329677581787, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-UuX1xuaiiE/3", "video_id": "-UuX1xuaiiE", "clip_id": "3", "feature_path": "features_v2/-UuX1xuaiiE__3.npz", "source_video_sha256": "eddb408f25bdc13e6de2fb74caeed709cb7a8f00640e7897135330437f66a2aa", "feature_sha256": "1efdc8d158b2480f4829148cd5eabc9236175782b056575467486af004ac81c4", "duration_s": 4.1300129890441895, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-UuX1xuaiiE/6", "video_id": "-UuX1xuaiiE", "clip_id": "6", "feature_path": "features_v2/-UuX1xuaiiE__6.npz", "source_video_sha256": "f0131ac00e44410fbd32c547a0d421c2791172434fc0203bb969abe14a530532", "feature_sha256": "c6bf473a36b950f74aacf8e8d2f1153e7591430d4ad57da3a1adb2da79b72198", "duration_s": 8.113997459411621, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-a55Q6RWvTA/3", "video_id": "-a55Q6RWvTA", "clip_id": "3", "feature_path": "features_v2/-a55Q6RWvTA__3.npz", "source_video_sha256": "15d029fc15f50b268b98f1e8abc65e45582e638c13a018d7aa74a18373275946", "feature_sha256": "c1989fc2b6e742d75a029b7f912e0fdd73cfdbad35bcceae0e0e39ae0582b611", "duration_s": 22.15397071838379, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-aNfi7CP8vM/7", "video_id": "-aNfi7CP8vM", "clip_id": "7", "feature_path": "features_v2/-aNfi7CP8vM__7.npz", "source_video_sha256": "0b6389f45bb966113c65a11998c6e7facdd24c8c42acf2e8cf32e0f2139402e1", "feature_sha256": "dcb20301dbb921eeaee67499779334261ac38cee42e04215b54bdd790eb04d71", "duration_s": 8.694987297058105, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-aqamKhZ1Ec/0", "video_id": "-aqamKhZ1Ec", "clip_id": "0", "feature_path": "features_v2/-aqamKhZ1Ec__0.npz", "source_video_sha256": "6f6e674bbba4353399e9e7825217f37e6d8ca1adf9675f33cf37106f31c55548", "feature_sha256": "686f7710f18ce27ca864c44021246fdedd4d41b4620b210b2bae988f4011561e", "duration_s": 10.966667175292969, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-dxfTGcXJoc/1", "video_id": "-dxfTGcXJoc", "clip_id": "1", "feature_path": "features_v2/-dxfTGcXJoc__1.npz", "source_video_sha256": "b5ffdc98a4a98b8f0aae55dee5fed66a43bcb129f47a250a004c1cc74e572595", "feature_sha256": "bbc68c9e5c1880673cfb77f48aa3dcdf10829b07cd64e6b00a8b7a3f8694b9a8", "duration_s": 16.16100311279297, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-dxfTGcXJoc/0", "video_id": "-dxfTGcXJoc", "clip_id": "0", "feature_path": "features_v2/-dxfTGcXJoc__0.npz", "source_video_sha256": "04bd0be907fb46c613f926fe1b06bc2c3b30881c293156510baa34a4105ade55", "feature_sha256": "76423f4fa8a57e214ecbebd02bc72e06eaaa8b8e25f40cca4fdb9296a1e30673", "duration_s": 20.5, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-dxfTGcXJoc/2", "video_id": "-dxfTGcXJoc", "clip_id": "2", "feature_path": "features_v2/-dxfTGcXJoc__2.npz", "source_video_sha256": "46a5e523b5dd00364c99a3bcbc6fae4c80e1382a84b5b5e681c46eb074ed10f7", "feature_sha256": "fa9effa2f4efcf00296196e2ab90d819de5c6cab85f4075d8f2ae419743f57b5", "duration_s": 16.697982788085938, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-dxfTGcXJoc/6", "video_id": "-dxfTGcXJoc", "clip_id": "6", "feature_path": "features_v2/-dxfTGcXJoc__6.npz", "source_video_sha256": "55c1ff86729ab21da743a5107ed07b220e41b4193cd1c0ba9f009e65c3ab0513", "feature_sha256": "28beb6292687bfd0bc6a90e97083bfb2e7eaf133447dd9bf2a9be974d5b9fc4b", "duration_s": 12.735026359558105, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/26", "video_id": "-egA8-b7-3M", "clip_id": "26", "feature_path": "features_v2/-egA8-b7-3M__26.npz", "source_video_sha256": "11f78aed680ea9c5862a15e04ff6c1f8776c46d29c98b68d28773ff39d959685", "feature_sha256": "8f752d0ae41def9d6cfa650d06674143d8d743be33a702a947d181ff39c08e59", "duration_s": 6.030990123748779, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/17", "video_id": "-egA8-b7-3M", "clip_id": "17", "feature_path": "features_v2/-egA8-b7-3M__17.npz", "source_video_sha256": "f967e81e0edd51c3700671721afb3fbecbbc32652f27d2db40ea444e8d80c68f", "feature_sha256": "d18309c6de9e1218a6d8133d9f79c2df0a4a582022c1417a8ccd7ae71fbab8ab", "duration_s": 7.271028995513916, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/18", "video_id": "-egA8-b7-3M", "clip_id": "18", "feature_path": "features_v2/-egA8-b7-3M__18.npz", "source_video_sha256": "fd4bc95a6adfb16a9588b7ed65cc2812186ce3d607804dc76f4486312951ef90", "feature_sha256": "dde916a05af47a5487e06c2749299e5bf2fc67fadb36a575c20c66065ac03176", "duration_s": 8.386002540588379, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/16", "video_id": "-egA8-b7-3M", "clip_id": "16", "feature_path": "features_v2/-egA8-b7-3M__16.npz", "source_video_sha256": "4c015f85e3bd901ecedde8c0c2ca4a756d9a77dfe4177625c481581f2037ff9c", "feature_sha256": "6baba8aa2db080c79a9b72bf3c0814e66423a9d5a56216a09ffa699bc359b104", "duration_s": 5.538021087646484, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/13", "video_id": "-egA8-b7-3M", "clip_id": "13", "feature_path": "features_v2/-egA8-b7-3M__13.npz", "source_video_sha256": "cbd627c225ebca37521ae238cb9ec254cad236822f4f37ebfbb039e6c56aea4c", "feature_sha256": "9133432494bb49a3e563cec8420925d5522ae007917972f82991886d55be47ba", "duration_s": 4.18398380279541, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/1", "video_id": "-egA8-b7-3M", "clip_id": "1", "feature_path": "features_v2/-egA8-b7-3M__1.npz", "source_video_sha256": "589a7989b1b4888568c31614a2d6beefba88fb87289c85b94df2d8051500403b", "feature_sha256": "71288235e60086aa6ab363066f68cf77c57048c65cf3a6b45f32c227381e7061", "duration_s": 10.266016006469727, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/6", "video_id": "-egA8-b7-3M", "clip_id": "6", "feature_path": "features_v2/-egA8-b7-3M__6.npz", "source_video_sha256": "2e88a00d5e55863aa956b4b0949fc0e4f2975ccfd83b3194957f6fca86ecfce0", "feature_sha256": "0cbad1d28be25446822090f74bf6d15e4e8d3c16e03cc4c863597e457b6bfb5f", "duration_s": 6.264974117279053, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/9", "video_id": "-egA8-b7-3M", "clip_id": "9", "feature_path": "features_v2/-egA8-b7-3M__9.npz", "source_video_sha256": "bcd643f328616312eae9b28e6c9aabbe8acaec3e4c4be58daeb45e3eee4d973c", "feature_sha256": "295b96b7d73d49dd83272d84f7b5741daa654d4323f2a003e94a21aa0726cf2a", "duration_s": 5.0899739265441895, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-egA8-b7-3M/20", "video_id": "-egA8-b7-3M", "clip_id": "20", "feature_path": "features_v2/-egA8-b7-3M__20.npz", "source_video_sha256": "719ef133920e74b3ea33925d2bca0270b062199116867568f88ac83cadcd74e8", "feature_sha256": "3ef6eab499a10dd0f6181a3b4175bbcd5c4d629206e462ab642d8e2c58e689e9", "duration_s": 6.644987106323242, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-iRBcNs9oI8/3", "video_id": "-iRBcNs9oI8", "clip_id": "3", "feature_path": "features_v2/-iRBcNs9oI8__3.npz", "source_video_sha256": "39c547acd1a8da2ebc6c6ab008191a5676420398a974cd9611cad087f0ccec50", "feature_sha256": "6843ce1b4b5a47d13b7d9cdcc6426141def4897da2ea0e7e325da54f3ce14907", "duration_s": 5.620999813079834, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-iRBcNs9oI8/7", "video_id": "-iRBcNs9oI8", "clip_id": "7", "feature_path": "features_v2/-iRBcNs9oI8__7.npz", "source_video_sha256": "cc7b1c7a06ec41d72007b5c42fb8036f30b66d5cf76e3f093160c22fc5e38081", "feature_sha256": "d7421743e7b1804ea0bb71568d9d3972fe5a908f699443a4125291f5bb26171b", "duration_s": 4.104000091552734, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-iRBcNs9oI8/6", "video_id": "-iRBcNs9oI8", "clip_id": "6", "feature_path": "features_v2/-iRBcNs9oI8__6.npz", "source_video_sha256": "030b0b8d8d4ae47799432ab71b66fcc881f077c5b0feef892c1f146dc0a592e3", "feature_sha256": "d24c023d23ecd8a1715e2759f47b155006e5eb61c2e4d25e436b62af400574fe", "duration_s": 2.9030001163482666, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-iRBcNs9oI8/9", "video_id": "-iRBcNs9oI8", "clip_id": "9", "feature_path": "features_v2/-iRBcNs9oI8__9.npz", "source_video_sha256": "352bdcc73d3622d6abac1b09f2039b211d15b9bf7acee66f0b07783c9b7a4c74", "feature_sha256": "fe85860d993e18a09afb8293bc00ac76b3ad673db783d4a158ab8062b697a2a4", "duration_s": 3.4159998893737793, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-iRBcNs9oI8/8", "video_id": "-iRBcNs9oI8", "clip_id": "8", "feature_path": "features_v2/-iRBcNs9oI8__8.npz", "source_video_sha256": "72a49e3da2862addcde033bd2fbae957d086533664ca4dcbccd3a851bcd50412", "feature_sha256": "230f08e63b84fc8eea864ef801a59dbdae7b40800e58731184c2f41c70f35c09", "duration_s": 8.093000411987305, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-lzEya4AM_4/5", "video_id": "-lzEya4AM_4", "clip_id": "5", "feature_path": "features_v2/-lzEya4AM_4__5.npz", "source_video_sha256": "3b74de8d0e66fd754594f05985e5af3480adcc03555f79225d3b727cb4bed043", "feature_sha256": "a7c6dc3a59764e6353c1fddcaee94abe8c05335fd54803e1f5dbc5c20a21c277", "duration_s": 7.675000190734863, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-lzEya4AM_4/6", "video_id": "-lzEya4AM_4", "clip_id": "6", "feature_path": "features_v2/-lzEya4AM_4__6.npz", "source_video_sha256": "ff5da5afb551001d58100ed2471caea51c741046d5396957efbf40082307ca18", "feature_sha256": "cafd2cbc61f1e95896fb3bd46a456ba384ea6f3708a659f87749f9ef7432be1b", "duration_s": 14.7919921875, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-mJ2ud6oKI8/1", "video_id": "-mJ2ud6oKI8", "clip_id": "1", "feature_path": "features_v2/-mJ2ud6oKI8__1.npz", "source_video_sha256": "c8e3acb4ca08679796c4c8cdfcd8c2b3cffc0465015efaffa1eccdb55ff4f45b", "feature_sha256": "cd2c39bb1ca30648413e10739b052627f0b660127f461db37b3cfc93c72e8155", "duration_s": 6.103000164031982, "status": "partial", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-mJ2ud6oKI8/2", "video_id": "-mJ2ud6oKI8", "clip_id": "2", "feature_path": "features_v2/-mJ2ud6oKI8__2.npz", "source_video_sha256": "c41790f8b75f886bd1f5d6d72cbab1985306d0b715e5ae47203f3d0c8a3e7b28", "feature_sha256": "948618c6648a9a00753aff26cb65f3b9ef3bbe075f7e3d5a4c496eae541531dc", "duration_s": 5.730999946594238, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-mJ2ud6oKI8/6", "video_id": "-mJ2ud6oKI8", "clip_id": "6", "feature_path": "features_v2/-mJ2ud6oKI8__6.npz", "source_video_sha256": "134a3a4ebbc423760133b0da0e90cfe84d75ea85df3968000afd84153c5d828d", "feature_sha256": "ca445e27d85ecd9578d39c37d5ad93b8c6113bf264861a6945242e202cbf7e1f", "duration_s": 2.256999969482422, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-mJ2ud6oKI8/9", "video_id": "-mJ2ud6oKI8", "clip_id": "9", "feature_path": "features_v2/-mJ2ud6oKI8__9.npz", "source_video_sha256": "df3442f3ed8f894b507923e87e426ae9636790a4a502d00c350a215270cc3e47", "feature_sha256": "1610e8626eec162785662e4445b5a8581abab6425a7956bbfe147742d06aad7c", "duration_s": 7.0329999923706055, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-mJ2ud6oKI8/8", "video_id": "-mJ2ud6oKI8", "clip_id": "8", "feature_path": "features_v2/-mJ2ud6oKI8__8.npz", "source_video_sha256": "6af6614c9838417a48c4817f4dd5bf1e6669eb5f78f983e9a68adc113fcdc7cc", "feature_sha256": "f262bc1e640742b43f80bec55ad965d31d2ccadfc7f5c921848ff5f41c76877a", "duration_s": 4.191999912261963, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-mqbVkbCndg/0", "video_id": "-mqbVkbCndg", "clip_id": "0", "feature_path": "features_v2/-mqbVkbCndg__0.npz", "source_video_sha256": "cdeba953bba30907814acf72f3e35582dec263eaa25bdd922dad7fafe02e4dda", "feature_sha256": "4ccb2b3e910eeb9a00598d3204a7214de9b453636d1d8c026419aeaee157a5c6", "duration_s": 6.466667175292969, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-t217m2on-s/2", "video_id": "-t217m2on-s", "clip_id": "2", "feature_path": "features_v2/-t217m2on-s__2.npz", "source_video_sha256": "6713a983112173204502dc5adbb886bc6c38554b4627698611931420c47663a8", "feature_sha256": "9f325b13ee0982de9e94c5ce7ba483a970251ba88b86aac2fb7a67189978d5b2", "duration_s": 5.6860032081604, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-t217m2on-s/7", "video_id": "-t217m2on-s", "clip_id": "7", "feature_path": "features_v2/-t217m2on-s__7.npz", "source_video_sha256": "41c76b8a733ecb780d56348f55d00f0ba0c07f44f46e93044e62d876710180b7", "feature_sha256": "9fd4771029300d09aefe043ea3ce447828c34de03d8a238abdf2f83cb6b96bc7", "duration_s": 17.183008193969727, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-tANM6ETl_M/3", "video_id": "-tANM6ETl_M", "clip_id": "3", "feature_path": "features_v2/-tANM6ETl_M__3.npz", "source_video_sha256": "6d3af064c060dad3816a9e1dfa00101faebd8b7da7d8ceea85b0bc0fca70abd6", "feature_sha256": "dc3c9acd5f216c41559a001c278266aa2fe777f99cb45acd9925444c71bc84d9", "duration_s": 6.758008003234863, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-tPCytz4rww/11", "video_id": "-tPCytz4rww", "clip_id": "11", "feature_path": "features_v2/-tPCytz4rww__11.npz", "source_video_sha256": "6dc09d02467baaef67594ff32cffb01f8ed1565cda4c5abd161b8261a51e2e5d", "feature_sha256": "03b82e99bc518fc778ea4447b4564b8cbb3b3d132b8316d75c65ff1e50e66769", "duration_s": 5.466991901397705, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-tPCytz4rww/10", "video_id": "-tPCytz4rww", "clip_id": "10", "feature_path": "features_v2/-tPCytz4rww__10.npz", "source_video_sha256": "758183192cbd5f0f20823d26ece3ddee40dec0aadec35247b2495510a53f52f8", "feature_sha256": "11be01f908f0545b1cd3f75380c9c51a731d8b5f087b9d34f5670e02766da840", "duration_s": 4.788997173309326, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-tPCytz4rww/12", "video_id": "-tPCytz4rww", "clip_id": "12", "feature_path": "features_v2/-tPCytz4rww__12.npz", "source_video_sha256": "e3c7f9cd67ad2d20fef997696dc278361f97f027db58762e22104e5720d509d5", "feature_sha256": "da94125e6235aecdfd31615210d8bb22e56b54882ced8e548c09408fa1f14191", "duration_s": 11.863997459411621, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-tPCytz4rww/16", "video_id": "-tPCytz4rww", "clip_id": "16", "feature_path": "features_v2/-tPCytz4rww__16.npz", "source_video_sha256": "8b3f82f9628792eebaf8c227becc2ebeba59bb8abb668bcca86e07fb1a992a93", "feature_sha256": "5fd4724db408e31ee12e37ddf109dcaf9899521f6641db9e6ee8b144f5625b00", "duration_s": 7.205989837646484, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-tPCytz4rww/18", "video_id": "-tPCytz4rww", "clip_id": "18", "feature_path": "features_v2/-tPCytz4rww__18.npz", "source_video_sha256": "eece70451e9c37a4b5f375bf646f66e09113fe3467f35bf1720cc14d986c1776", "feature_sha256": "92bac66566bcd80659c7e96a6d47014c994bb8d815cd906be5cccab86e50b82b", "duration_s": 6.644987106323242, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-vxjVxOeScU/4", "video_id": "-vxjVxOeScU", "clip_id": "4", "feature_path": "features_v2/-vxjVxOeScU__4.npz", "source_video_sha256": "00282df9a314394f19d616d561bd1916d1d30dca4c814dfb43630ca164f4c719", "feature_sha256": "036473f43edafbd41626317599d4df6e6847def482a18c760e45f77f822b45f4", "duration_s": 9.127017974853516, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wMB_hJL-3o/7", "video_id": "-wMB_hJL-3o", "clip_id": "7", "feature_path": "features_v2/-wMB_hJL-3o__7.npz", "source_video_sha256": "04a73d73fd150b07edab8e652fd14c9cb4a0f6e017ea97b17e8146e377b9fae7", "feature_sha256": "e091d71d9bb99da782ea268fb1652ee8ad0a4b256af35f919beed8b52309000d", "duration_s": 6.044010162353516, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wny0OAz3g8/1", "video_id": "-wny0OAz3g8", "clip_id": "1", "feature_path": "features_v2/-wny0OAz3g8__1.npz", "source_video_sha256": "c5535859f129ce04da5f5b5104d02d4168057b9d947a5349d423278ad4f3d8e8", "feature_sha256": "bdbd64a96b68b7472c5a6d35ec8f973eb5c28c4b8af04a3cd6f33176c0db4d41", "duration_s": 6.844009876251221, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wny0OAz3g8/0", "video_id": "-wny0OAz3g8", "clip_id": "0", "feature_path": "features_v2/-wny0OAz3g8__0.npz", "source_video_sha256": "1cc21b4432e2f4b592284ee40558a18acc7d208019117aebdb2f6a1b67ea198d", "feature_sha256": "8985259ed866ff2f7a65967434022d2a40a9006742175ce7f7fb88cdc8513c02", "duration_s": 3.3333330154418945, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wny0OAz3g8/3", "video_id": "-wny0OAz3g8", "clip_id": "3", "feature_path": "features_v2/-wny0OAz3g8__3.npz", "source_video_sha256": "ea917c506bc9fa39460f2b722f7c416f646312bb17cd74baa618905b54837bce", "feature_sha256": "3bc79a7fe97c226b5cf3bddd080ec9875daf3ec94b16582e39dcddcc7f77fcfc", "duration_s": 6.146028995513916, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wny0OAz3g8/2", "video_id": "-wny0OAz3g8", "clip_id": "2", "feature_path": "features_v2/-wny0OAz3g8__2.npz", "source_video_sha256": "1825fb37db2e914fb616cd80aebd613afd873eecb7edd4d0b53158bfda0e522a", "feature_sha256": "d9012975be4a0faccd98b3ed4462c664157184b13034c342ed0916667b7074f6", "duration_s": 6.686978816986084, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wny0OAz3g8/5", "video_id": "-wny0OAz3g8", "clip_id": "5", "feature_path": "features_v2/-wny0OAz3g8__5.npz", "source_video_sha256": "6c6a8a4cba654b25190ab1793bfae81c3d2159ecd6cbc02d3fcbd598543acce0", "feature_sha256": "96f35b0e59d87b842d8e3f73a9601e0c4aab770f47984d48cc4409f41b783c3f", "duration_s": 6.9119791984558105, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wny0OAz3g8/7", "video_id": "-wny0OAz3g8", "clip_id": "7", "feature_path": "features_v2/-wny0OAz3g8__7.npz", "source_video_sha256": "cbf9cac1048ce191aef781d0c4563eb97f9f3ddd52eb1f9551fa78d31d70e7cd", "feature_sha256": "8e08e91628357e41dbd627d334780086ae59dbc3fdc948bb0d35c9a4581bae8c", "duration_s": 8.06796932220459, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-wny0OAz3g8/9", "video_id": "-wny0OAz3g8", "clip_id": "9", "feature_path": "features_v2/-wny0OAz3g8__9.npz", "source_video_sha256": "b76f70fe783ee76bf1b637137226c98e67d13a6b15ec725894175b36cd76a136", "feature_sha256": "12f2eed3652cdca9c0cb7ba62acd555913433a5782a9fc3bd4e3287cc390fbc7", "duration_s": 6.2130208015441895, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-571d8cVauQ/0", "video_id": "-571d8cVauQ", "clip_id": "0", "feature_path": "features_v2/-571d8cVauQ__0.npz", "source_video_sha256": "defc62e1530d9b0cd27f2acfe042b71dd84d7e8c85f1df261f04912091976088", "feature_sha256": "9c38a6e6628f67352d27716703f7be63cb4d958ba4ba8eaf697ba3b637aeaa42", "duration_s": 5.066667079925537, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-571d8cVauQ/5", "video_id": "-571d8cVauQ", "clip_id": "5", "feature_path": "features_v2/-571d8cVauQ__5.npz", "source_video_sha256": "e304c03fac1f477823fe0926c4fb421b9c4e4091f85718b729afe856c24a7d3e", "feature_sha256": "1397887ed11ede3e9cab0566d564fd11014bc15e24aa2a6f5675ea452c9d149c", "duration_s": 15.272981643676758, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-I_e4mIh0yE/1", "video_id": "-I_e4mIh0yE", "clip_id": "1", "feature_path": "features_v2/-I_e4mIh0yE__1.npz", "source_video_sha256": "8453a966d4bd1f0179eb86a35cd34c0864a3a3bc2fa86f37783060cc0d14f5df", "feature_sha256": "5f2b4811b0ae11e9e00f74d0f6dfaff9ee474c13b3033793ad73cbf233a2f60e", "duration_s": 7.622004985809326, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-I_e4mIh0yE/3", "video_id": "-I_e4mIh0yE", "clip_id": "3", "feature_path": "features_v2/-I_e4mIh0yE__3.npz", "source_video_sha256": "a015f02ae51d74c42ad27e2a0831263a303f0658ac8d984819e27ad61342b23e", "feature_sha256": "692898c2d7d4f91ef25c4ec60364ee7cc7c64baff8d09a789600f6b9fb585295", "duration_s": 9.163021087646484, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-UacrmKiTn4/10", "video_id": "-UacrmKiTn4", "clip_id": "10", "feature_path": "features_v2/-UacrmKiTn4__10.npz", "source_video_sha256": "00312e0602dbeac970de5d7dd0e8a970e514fe45d7fe6625b4f454b299e36360", "feature_sha256": "2b9b15073d07d5a042ff7e4d89f4819d99efcc98e96b5fe2d42e26b6421ed01c", "duration_s": 7.361979007720947, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-UacrmKiTn4/4", "video_id": "-UacrmKiTn4", "clip_id": "4", "feature_path": "features_v2/-UacrmKiTn4__4.npz", "source_video_sha256": "a7f2ab0d6ee2e2a2d5cd4239f03e6cac67c618ddb744891323df3ff87e29af84", "feature_sha256": "7d6be8f06193a82c27fde3e99106fcd518632cf61a19a66f567d8327057e280b", "duration_s": 4.741015911102295, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-hnBHBN8p5A/7", "video_id": "-hnBHBN8p5A", "clip_id": "7", "feature_path": "features_v2/-hnBHBN8p5A__7.npz", "source_video_sha256": "0ec6fb76226315d3fafbb483e3500e60cc8a6729617b10e4b4b76163595080fb", "feature_sha256": "9ebd1d3b4bbb4cbb6e41ef9f67e049323c7b13a99f991d5f44fc56216fe610f6", "duration_s": 7.372000217437744, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-hnBHBN8p5A/6", "video_id": "-hnBHBN8p5A", "clip_id": "6", "feature_path": "features_v2/-hnBHBN8p5A__6.npz", "source_video_sha256": "997f6808ac3973ddd71800e91c8e10755c10b5037186d84348a79e00d672fdc7", "feature_sha256": "a778554ac2390ad44dc63d88a22d0cbcf817eec084022dbf5e48a2a3be52236f", "duration_s": 6.401000022888184, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-qDkUB0GgYY/6", "video_id": "-qDkUB0GgYY", "clip_id": "6", "feature_path": "features_v2/-qDkUB0GgYY__6.npz", "source_video_sha256": "ab19bf4a761d3919b7113b1104ccaadbbf0c4e7b45694904d094280a2c329c8b", "feature_sha256": "d157258aff14c527b21565f98c368c753ff20f31aa0cbe051f1d24c57a7412e2", "duration_s": 4.677995204925537, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-uywlfIYOS8/4", "video_id": "-uywlfIYOS8", "clip_id": "4", "feature_path": "features_v2/-uywlfIYOS8__4.npz", "source_video_sha256": "fde46f5222cf5cfec7af9c93214c972ce2d686b0bf9c90a291021601968986f9", "feature_sha256": "d07fd83e0727367f03d2ae5e0ed623da12fd1946fc010dbdd93d5148e0d4730c", "duration_s": 6.044987201690674, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-6rXp3zJ3kc/8", "video_id": "-6rXp3zJ3kc", "clip_id": "8", "feature_path": "features_v2/-6rXp3zJ3kc__8.npz", "source_video_sha256": "59b77a0f873b7fa95e7327b93d64bb5a4bdae84039321988ef70bba8e63ee1ae", "feature_sha256": "becdc042c9ef64c388bde8e5b3a98ac8d99782afcb454c5d8feab4289ab1523c", "duration_s": 12.805012702941895, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-9y-fZ3swSY/0", "video_id": "-9y-fZ3swSY", "clip_id": "0", "feature_path": "features_v2/-9y-fZ3swSY__0.npz", "source_video_sha256": "29a1fd181293cdbe24e50edcad51e3cc0f27a5824d840bcc39d453b272fb6e3c", "feature_sha256": "0c96186d24ab43623baf90a1e626f66f7cc1695c0b8b70ad3ca63a90a8110f52", "duration_s": 6.8333330154418945, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-9y-fZ3swSY/4", "video_id": "-9y-fZ3swSY", "clip_id": "4", "feature_path": "features_v2/-9y-fZ3swSY__4.npz", "source_video_sha256": "fe04a26710e85c58bdaad5a48618c7e0f742d82b0d920eb4a046c8f14a6921dc", "feature_sha256": "6cff9072da7459fdf9c4a0dda4c687b8b2c525ba3af893be8b4b51943cff0e88", "duration_s": 2.8210289478302, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-9y-fZ3swSY/8", "video_id": "-9y-fZ3swSY", "clip_id": "8", "feature_path": "features_v2/-9y-fZ3swSY__8.npz", "source_video_sha256": "6140d031e614b62a5d8df73346fef2417c28cee3501310aa95aa40d358b4b256", "feature_sha256": "cd54043f6a090b25e8ba26bec6b6a399b17e798ca08161fd33239be6f5761a50", "duration_s": 4.988996982574463, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-AUZQgSxyPQ/2", "video_id": "-AUZQgSxyPQ", "clip_id": "2", "feature_path": "features_v2/-AUZQgSxyPQ__2.npz", "source_video_sha256": "e09034b30bb4f8fb9f9c2568afc20f2a764e327fe6ef4e13f21804eaac51bb7b", "feature_sha256": "6b3fad600f0199347f255e07d223482885cd794a47c0d7ac2ff16c86232495d7", "duration_s": 22.511003494262695, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-HeZS2-Prhc/2", "video_id": "-HeZS2-Prhc", "clip_id": "2", "feature_path": "features_v2/-HeZS2-Prhc__2.npz", "source_video_sha256": "edb0fa22fdfbd990c8174764af78670bd2ecad799240d35d96a1124a3cd9dc0c", "feature_sha256": "a545e12987ed9776f6799e87297422df5b8ab3bacade90fcbf98bea7e28ad1c5", "duration_s": 8.261979103088379, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-MeTTeMJBNc/0", "video_id": "-MeTTeMJBNc", "clip_id": "0", "feature_path": "features_v2/-MeTTeMJBNc__0.npz", "source_video_sha256": "54e2097f7bdb455d243e72254e5af7ed16160739f2f3cfd22f57d2729ec1acbe", "feature_sha256": "ef950e662a99c32820f5b841a570702e5d24ae392ab8d553be90c669f1abc4e1", "duration_s": 9.300000190734863, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-MeTTeMJBNc/13", "video_id": "-MeTTeMJBNc", "clip_id": "13", "feature_path": "features_v2/-MeTTeMJBNc__13.npz", "source_video_sha256": "80c5d996e5d7b61c58e1fe32d706efcaf80c8c8cddb5c37a5c66d527786c2a44", "feature_sha256": "ac464b5b9963d9934a9e9cbadb993297b39724ffb7c9afef74f1d42600d5b65f", "duration_s": 5.430013179779053, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-MeTTeMJBNc/7", "video_id": "-MeTTeMJBNc", "clip_id": "7", "feature_path": "features_v2/-MeTTeMJBNc__7.npz", "source_video_sha256": "40716dc7402fd6398038eded58ce939ec76be6afbeea346b968bf4c427088f3a", "feature_sha256": "658af7fb69567bc1a1c6f6e26aa7f9c27354169c220ab987624997266ccf502f", "duration_s": 10.51699161529541, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-RfYyzHpjk4/11", "video_id": "-RfYyzHpjk4", "clip_id": "11", "feature_path": "features_v2/-RfYyzHpjk4__11.npz", "source_video_sha256": "96e481cd732001239639c8cb4e7f94e570925e5a15940920ffab0cec1f13f904", "feature_sha256": "382456355758265669dff9e465e4cc02bb8c6724463a2e5967e7f8893c63b4e4", "duration_s": 5.238996982574463, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-RfYyzHpjk4/8", "video_id": "-RfYyzHpjk4", "clip_id": "8", "feature_path": "features_v2/-RfYyzHpjk4__8.npz", "source_video_sha256": "ed8c672c532b8c7d1c830c82d6e2c14fcb3bbd3dc1a86670a03a6c8e83c02f3c", "feature_sha256": "f8217c39db4c367c69a617664528cad9b92ca28f532e755031a92214afa77377", "duration_s": 4.588996887207031, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-RfYyzHpjk4/2", "video_id": "-RfYyzHpjk4", "clip_id": "2", "feature_path": "features_v2/-RfYyzHpjk4__2.npz", "source_video_sha256": "5e428ee22de58c9d6561b9b4353bef9dfb811a5841d9ab07b5bc453d697ad17f", "feature_sha256": "63203a3e910c0e8121c88c86f50dfbbf99b316558376c589760573bb99a30162", "duration_s": 5.516016006469727, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-UUCSKoHeMA/0", "video_id": "-UUCSKoHeMA", "clip_id": "0", "feature_path": "features_v2/-UUCSKoHeMA__0.npz", "source_video_sha256": "3f9792888ec8e969f61cfb6cb4de7bdf7ef8944afe0a9d2a9d13586e1f13b898", "feature_sha256": "987dfd55d6b20aad24e7dfc246b8118d5428ec936e103b104a21e19d49b1cbdb", "duration_s": 7.800000190734863, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-ri04Z7vwnc/0", "video_id": "-ri04Z7vwnc", "clip_id": "0", "feature_path": "features_v2/-ri04Z7vwnc__0.npz", "source_video_sha256": "93021c70c8fad20ad3fded80e7fc790a2f9c96835de58a3b694c6906de42684b", "feature_sha256": "9631590e28e7e4b4d153058cd781188ceecf7e6a4f799ea21a649dbb14b42a05", "duration_s": 2.806999921798706, "status": "partial", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-ri04Z7vwnc/2", "video_id": "-ri04Z7vwnc", "clip_id": "2", "feature_path": "features_v2/-ri04Z7vwnc__2.npz", "source_video_sha256": "f5993ce3a6ee56298462c08692a96f78c7d914d7264257ed401de7d6e17fd148", "feature_sha256": "ec4a0add30cd5eba8030fdadf54c66679192a3af67c6b35a518e277b9341f72c", "duration_s": 6.0269999504089355, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-ri04Z7vwnc/5", "video_id": "-ri04Z7vwnc", "clip_id": "5", "feature_path": "features_v2/-ri04Z7vwnc__5.npz", "source_video_sha256": "2a5e4319bf592a18c8eb5117f7c7ec30c6c79528f9eac9fe4fb85bf70f277736", "feature_sha256": "c4b36fc3e52553e01ed6e39ff7bc82d3b346f6895688a7473d059fb06ca68454", "duration_s": 3.5450000762939453, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-s9qJ7ATP7w/1", "video_id": "-s9qJ7ATP7w", "clip_id": "1", "feature_path": "features_v2/-s9qJ7ATP7w__1.npz", "source_video_sha256": "a33e8f82a185bcb7648b523927308cdc2142e3076479ba2b45cd0e828636fc09", "feature_sha256": "8657717d40999eb17c2a865470cf52dff151f9db8a0b122fa058ac43f8bed0db", "duration_s": 4.7919921875, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-s9qJ7ATP7w/0", "video_id": "-s9qJ7ATP7w", "clip_id": "0", "feature_path": "features_v2/-s9qJ7ATP7w__0.npz", "source_video_sha256": "f2324dbc6debe0d1e4254e6f943523373e3cbb30d8d0555413ce3a7b370a4af5", "feature_sha256": "301cc976bec37d768ef66b77e6ac982ca30f0837b6f4ea2afe9baa9c25e2b664", "duration_s": 6.800000190734863, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-s9qJ7ATP7w/5", "video_id": "-s9qJ7ATP7w", "clip_id": "5", "feature_path": "features_v2/-s9qJ7ATP7w__5.npz", "source_video_sha256": "bd35eb8de64ce1f8b27921294598f7cf5bb971255738f51d06458c7096c58eae", "feature_sha256": "476746914805af458c0c0ca41d9da33ec55b7bfa280018c4aec2cbd780f4f0de", "duration_s": 8.561002731323242, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-s9qJ7ATP7w/4", "video_id": "-s9qJ7ATP7w", "clip_id": "4", "feature_path": "features_v2/-s9qJ7ATP7w__4.npz", "source_video_sha256": "76e0672ffce5857a789b41fb10606402f7da58c33579b1032a8282f86fb35bc9", "feature_sha256": "734dea66c346fbdedb6db5861f502eff4a3dc46558466c8825ceb5413fa73d41", "duration_s": 4.427018165588379, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-s9qJ7ATP7w/7", "video_id": "-s9qJ7ATP7w", "clip_id": "7", "feature_path": "features_v2/-s9qJ7ATP7w__7.npz", "source_video_sha256": "5301f4403cf7cd299cc525d2f443392ac70fd70c3c9cd4a09406f2ba4df6734d", "feature_sha256": "1582ea53834aa456f77aa4384fd2e25514208ed51f53b4a99ff5e420b34cda27", "duration_s": 6.966015815734863, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-s9qJ7ATP7w/6", "video_id": "-s9qJ7ATP7w", "clip_id": "6", "feature_path": "features_v2/-s9qJ7ATP7w__6.npz", "source_video_sha256": "40b684fd1b4559f0f82e72f9b47f4ce7c5e15059fe8d0d8ab112637cd3386451", "feature_sha256": "25f32b57e6213d05d50b9e95b33e02fbbba587cae2bcce8189d7e911e5b9fbbc", "duration_s": 2.4749999046325684, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-s9qJ7ATP7w/8", "video_id": "-s9qJ7ATP7w", "clip_id": "8", "feature_path": "features_v2/-s9qJ7ATP7w__8.npz", "source_video_sha256": "1de4d91bd9d9fb508a3779658747da88460db0bdb1beefe7ea5b2c77edfd2239", "feature_sha256": "aceb43a00ad661ebc13dbb273fb0bbff55ee0416a99ca825ee046a98aa6a0299", "duration_s": 3.2949869632720947, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-yRb-Jum7EQ/1", "video_id": "-yRb-Jum7EQ", "clip_id": "1", "feature_path": "features_v2/-yRb-Jum7EQ__1.npz", "source_video_sha256": "2f16da1baa8660e5bfc608f5237cdd59725d16e57a19bc02c4556cd2c7129a46", "feature_sha256": "df02ec3e0707e3ae292d6ccabc0062d41f68e8dade44b0a04e7c93af52ab2a08", "duration_s": 29.288021087646484, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-yRb-Jum7EQ/5", "video_id": "-yRb-Jum7EQ", "clip_id": "5", "feature_path": "features_v2/-yRb-Jum7EQ__5.npz", "source_video_sha256": "6d6145293cf84d1fc8324dfa8d39465e2d7fc07affb67ff68deeabf6e3c410d9", "feature_sha256": "5d3083b3fcbdf232e5bed1955f4498481892a0c6f9fb74ff3c1460fa2975b3a1", "duration_s": 13.169010162353516, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} +{"sample_id": "-yRb-Jum7EQ/6", "video_id": "-yRb-Jum7EQ", "clip_id": "6", "feature_path": "features_v2/-yRb-Jum7EQ__6.npz", "source_video_sha256": "b51eb9ad06de804934cb6a315de591ba9ff7f8aa71bffff7f8001ab29945d989", "feature_sha256": "feda99761265679c062ad17106b6264a16e046a84d42fc0edc0c985c14232481", "duration_s": 11.241994857788086, "status": "success", "config_hash": "c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617"} diff --git a/submit/final/output/q1/features_v2/modality_summary.csv b/submit/final/output/q1/features_v2/modality_summary.csv new file mode 100644 index 0000000..ef81ffe --- /dev/null +++ b/submit/final/output/q1/features_v2/modality_summary.csv @@ -0,0 +1,301 @@ +sample_id,video_id,clip_id,modality,source_duration_s,observed_duration_s,native_length,native_dim,aligned_length,aligned_dim,alignment_type,granularity,mean_coverage,missing_reason,quality_available,quality_available_fraction,quality_path,query_map_path,probe_status,feature_path,source_hash,status,config_hash +-3g5yACwYnA/13,-3g5yACwYnA,13,text,5.5139970779418945,3.3000001311302185,15,768,56,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5984769459402665,none,True,1.0,-3g5yACwYnA__13.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-3g5yACwYnA__13.npz,aeb47627f59dce3d0a85a44ef35e4a3bc18211498e98c8c11cf60269646df24f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/13,-3g5yACwYnA,13,audio,5.5139970779418945,5.313856481700329,540,74,56,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9637031733219075,F0/HNR naturally undefined or unavailable in 315 rows; support masks retained,True,1.0,-3g5yACwYnA__13.npz::native_audio_*quality*,-3g5yACwYnA__13.npz::query_word_audio_H_time,content probe disabled,features_v2/-3g5yACwYnA__13.npz,aeb47627f59dce3d0a85a44ef35e4a3bc18211498e98c8c11cf60269646df24f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/13,-3g5yACwYnA,13,vision,5.5139970779418945,5.416500091552735,163,35,56,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9823182738381954,none,True,1.0,-3g5yACwYnA__13.npz::native_vision_*quality*,-3g5yACwYnA__13.npz::query_word_vision_H_time,content probe disabled,features_v2/-3g5yACwYnA__13.npz,aeb47627f59dce3d0a85a44ef35e4a3bc18211498e98c8c11cf60269646df24f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/3,-3g5yACwYnA,3,text,14.388997077941895,9.000000521540644,29,768,144,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6254779588035014,none,True,1.0,-3g5yACwYnA__3.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-3g5yACwYnA__3.npz,eff8cfefba2425155f2b72656829b34b23be8bfe1dcbece384154f56818aa26d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/3,-3g5yACwYnA,3,audio,14.388997077941895,14.072049074076318,1433,74,144,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9779728912203719,F0/HNR naturally undefined or unavailable in 947 rows; support masks retained,True,1.0,-3g5yACwYnA__3.npz::native_audio_*quality*,-3g5yACwYnA__3.npz::query_word_audio_H_time,content probe disabled,features_v2/-3g5yACwYnA__3.npz,eff8cfefba2425155f2b72656829b34b23be8bfe1dcbece384154f56818aa26d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/3,-3g5yACwYnA,3,vision,14.388997077941895,14.34950065612793,431,35,144,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9972550955705931,none,True,1.0,-3g5yACwYnA__3.npz::native_vision_*quality*,-3g5yACwYnA__3.npz::query_word_vision_H_time,content probe disabled,features_v2/-3g5yACwYnA__3.npz,eff8cfefba2425155f2b72656829b34b23be8bfe1dcbece384154f56818aa26d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/2,-3g5yACwYnA,2,text,9.394009590148926,5.659999758005142,14,768,94,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6025116010037427,none,True,1.0,-3g5yACwYnA__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-3g5yACwYnA__2.npz,0619c01f137d017c15c058176f18a575919189c7e81ebd2bfb993afdb86f3de7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/2,-3g5yACwYnA,2,audio,9.394009590148926,9.112644647424286,924,74,94,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9700484718453242,F0/HNR naturally undefined or unavailable in 440 rows; support masks retained,True,1.0,-3g5yACwYnA__2.npz::native_audio_*quality*,-3g5yACwYnA__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-3g5yACwYnA__2.npz,0619c01f137d017c15c058176f18a575919189c7e81ebd2bfb993afdb86f3de7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/2,-3g5yACwYnA,2,vision,9.394009590148926,9.249500274658203,278,35,94,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9846168652369418,none,True,1.0,-3g5yACwYnA__2.npz::native_vision_*quality*,-3g5yACwYnA__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-3g5yACwYnA__2.npz,0619c01f137d017c15c058176f18a575919189c7e81ebd2bfb993afdb86f3de7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/9,-3g5yACwYnA,9,text,8.816991806030273,7.46000128611922,21,768,89,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.8460937074952303,none,True,1.0,-3g5yACwYnA__9.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-3g5yACwYnA__9.npz,b962573b12ed1f06d5533f6427c80dce2bae3a99dd332f6d3fcb32c579f3f016,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/9,-3g5yACwYnA,9,audio,8.816991806030273,8.577734790943765,870,74,89,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9728640991905116,F0/HNR naturally undefined or unavailable in 438 rows; support masks retained,True,1.0,-3g5yACwYnA__9.npz::native_audio_*quality*,-3g5yACwYnA__9.npz::query_word_audio_H_time,content probe disabled,features_v2/-3g5yACwYnA__9.npz,b962573b12ed1f06d5533f6427c80dce2bae3a99dd332f6d3fcb32c579f3f016,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/9,-3g5yACwYnA,9,vision,8.816991806030273,8.749500274658203,263,35,89,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9923452881825398,none,True,1.0,-3g5yACwYnA__9.npz::native_vision_*quality*,-3g5yACwYnA__9.npz::query_word_vision_H_time,content probe disabled,features_v2/-3g5yACwYnA__9.npz,b962573b12ed1f06d5533f6427c80dce2bae3a99dd332f6d3fcb32c579f3f016,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3nNcZdcdvU/5,-3nNcZdcdvU,5,text,7.867969036102295,5.459999561309813,18,768,79,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6939528531767883,none,True,1.0,-3nNcZdcdvU__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-3nNcZdcdvU__5.npz,7ea53ad502b77be7d2ee4494dc23a9478c897792203cf887d546e83d584c1728,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3nNcZdcdvU/5,-3nNcZdcdvU,5,audio,7.867969036102295,7.693046254080695,779,74,79,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9777677338053868,F0/HNR naturally undefined or unavailable in 353 rows; support masks retained,True,1.0,-3nNcZdcdvU__5.npz::native_audio_*quality*,-3nNcZdcdvU__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-3nNcZdcdvU__5.npz,7ea53ad502b77be7d2ee4494dc23a9478c897792203cf887d546e83d584c1728,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3nNcZdcdvU/5,-3nNcZdcdvU,5,vision,7.867969036102295,7.867969036102295,236,35,79,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-3nNcZdcdvU__5.npz::native_vision_*quality*,-3nNcZdcdvU__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-3nNcZdcdvU__5.npz,7ea53ad502b77be7d2ee4494dc23a9478c897792203cf887d546e83d584c1728,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/2,-HwX2H8Z4hY,2,text,3.9820311069488525,3.3600000143051147,8,768,40,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.8437904988842853,1 word intervals are not reliable under the fixed-transcript CTC path,True,0.875,-HwX2H8Z4hY__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-HwX2H8Z4hY__2.npz,920a1052c09ebd1eedba6dd1ccfecd80f56f6f0fdcdd1f6dd32b0d90bcb89358,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/2,-HwX2H8Z4hY,2,audio,3.9820311069488525,3.8603403632705278,392,74,40,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9694400318807234,F0/HNR naturally undefined or unavailable in 206 rows; support masks retained,True,1.0,-HwX2H8Z4hY__2.npz::native_audio_*quality*,-HwX2H8Z4hY__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__2.npz,920a1052c09ebd1eedba6dd1ccfecd80f56f6f0fdcdd1f6dd32b0d90bcb89358,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/2,-HwX2H8Z4hY,2,vision,3.9820311069488525,1.1655313968658445,119,35,40,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.2926977126903733,OpenFace confidence/success gate rejected 85 native frames,True,0.29411764705882354,-HwX2H8Z4hY__2.npz::native_vision_*quality*,-HwX2H8Z4hY__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__2.npz,920a1052c09ebd1eedba6dd1ccfecd80f56f6f0fdcdd1f6dd32b0d90bcb89358,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/5,-HwX2H8Z4hY,5,text,5.6529951095581055,3.739999890327454,10,768,57,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6615961658986487,1 word intervals are not reliable under the fixed-transcript CTC path,True,0.9,-HwX2H8Z4hY__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-HwX2H8Z4hY__5.npz,8d61b7f5840a8bd403d9bc6c3cd49029ffc4e304cf9c7834735c1a6bb5fd369d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/5,-HwX2H8Z4hY,5,audio,5.6529951095581055,5.463188450078708,555,74,57,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9664237000385032,F0/HNR naturally undefined or unavailable in 299 rows; support masks retained,True,1.0,-HwX2H8Z4hY__5.npz::native_audio_*quality*,-HwX2H8Z4hY__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__5.npz,8d61b7f5840a8bd403d9bc6c3cd49029ffc4e304cf9c7834735c1a6bb5fd369d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/5,-HwX2H8Z4hY,5,vision,5.6529951095581055,2.0835000276565556,168,35,57,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.3685656872643965,OpenFace confidence/success gate rejected 105 native frames,True,0.375,-HwX2H8Z4hY__5.npz::native_vision_*quality*,-HwX2H8Z4hY__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__5.npz,8d61b7f5840a8bd403d9bc6c3cd49029ffc4e304cf9c7834735c1a6bb5fd369d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/6,-HwX2H8Z4hY,6,text,3.3580079078674316,1.8599998354911802,7,768,34,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5538997782385835,none,True,1.0,-HwX2H8Z4hY__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-HwX2H8Z4hY__6.npz,696576dcbd0dc42cb678fa40d8a0f0f419a3072bfa7662d653756ab29bba4c46,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/6,-HwX2H8Z4hY,6,audio,3.3580079078674316,3.221668961080345,326,74,34,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9593988607151103,F0/HNR naturally undefined or unavailable in 127 rows; support masks retained,True,1.0,-HwX2H8Z4hY__6.npz::native_audio_*quality*,-HwX2H8Z4hY__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__6.npz,696576dcbd0dc42cb678fa40d8a0f0f419a3072bfa7662d653756ab29bba4c46,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/6,-HwX2H8Z4hY,6,vision,3.3580079078674316,2.0334997773170467,99,35,34,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.60556729856198,OpenFace confidence/success gate rejected 38 native frames,True,0.6262626262626263,-HwX2H8Z4hY__6.npz::native_vision_*quality*,-HwX2H8Z4hY__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__6.npz,696576dcbd0dc42cb678fa40d8a0f0f419a3072bfa7662d653756ab29bba4c46,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/9,-HwX2H8Z4hY,9,text,7.733983993530273,5.199999988079072,22,768,78,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6723572213789218,none,True,1.0,-HwX2H8Z4hY__9.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-HwX2H8Z4hY__9.npz,90c18e627f27a12c14e21ba24c79edf3008762b4ac26874b490ad3f888381702,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/9,-HwX2H8Z4hY,9,audio,7.733983993530273,7.558725487088432,764,74,78,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9773391687145395,F0/HNR naturally undefined or unavailable in 287 rows; support masks retained,True,1.0,-HwX2H8Z4hY__9.npz::native_audio_*quality*,-HwX2H8Z4hY__9.npz::query_word_audio_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__9.npz,90c18e627f27a12c14e21ba24c79edf3008762b4ac26874b490ad3f888381702,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/9,-HwX2H8Z4hY,9,vision,7.733983993530273,7.366983890533446,232,35,78,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9525470826803062,OpenFace confidence/success gate rejected 11 native frames,True,0.9827586206896551,-HwX2H8Z4hY__9.npz::native_vision_*quality*,-HwX2H8Z4hY__9.npz::query_word_vision_H_time,content probe disabled,features_v2/-HwX2H8Z4hY__9.npz,90c18e627f27a12c14e21ba24c79edf3008762b4ac26874b490ad3f888381702,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/1,-NFrJFQijFE,1,text,5.745999813079834,4.380000352859496,16,768,58,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7622694910099262,none,True,1.0,-NFrJFQijFE__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-NFrJFQijFE__1.npz,d8fcf16c6eb51c56947ec3d0549c6f3bc7a0cf1fc5f5d89e8b38d4249a08db4f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/1,-NFrJFQijFE,1,audio,5.745999813079834,5.526589516046884,566,74,58,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.961815122838414,F0/HNR naturally undefined or unavailable in 489 rows; support masks retained,True,1.0,-NFrJFQijFE__1.npz::native_audio_*quality*,-NFrJFQijFE__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-NFrJFQijFE__1.npz,d8fcf16c6eb51c56947ec3d0549c6f3bc7a0cf1fc5f5d89e8b38d4249a08db4f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/1,-NFrJFQijFE,1,vision,5.745999813079834,0.0,142,35,58,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.0,OpenFace confidence/success gate rejected 142 native frames,False,0.0,-NFrJFQijFE__1.npz::native_vision_*quality*,-NFrJFQijFE__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-NFrJFQijFE__1.npz,d8fcf16c6eb51c56947ec3d0549c6f3bc7a0cf1fc5f5d89e8b38d4249a08db4f,failed_or_unavailable,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/2,-NFrJFQijFE,2,text,6.855999946594238,3.860000014305114,18,768,69,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5630105082224502,none,True,1.0,-NFrJFQijFE__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-NFrJFQijFE__2.npz,a54a5c144a64f5abd26b19aafa6cea8142ee08e9385fa508fa70c934f6b616c1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/2,-NFrJFQijFE,2,audio,6.855999946594238,6.544698231929059,670,74,69,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9545942653019097,F0/HNR naturally undefined or unavailable in 568 rows; support masks retained,True,1.0,-NFrJFQijFE__2.npz::native_audio_*quality*,-NFrJFQijFE__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-NFrJFQijFE__2.npz,a54a5c144a64f5abd26b19aafa6cea8142ee08e9385fa508fa70c934f6b616c1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/2,-NFrJFQijFE,2,vision,6.855999946594238,0.0,169,35,69,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.0,OpenFace confidence/success gate rejected 169 native frames,False,0.0,-NFrJFQijFE__2.npz::native_vision_*quality*,-NFrJFQijFE__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-NFrJFQijFE__2.npz,a54a5c144a64f5abd26b19aafa6cea8142ee08e9385fa508fa70c934f6b616c1,failed_or_unavailable,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/12,-THoVjtIkeU,12,text,14.896029472351074,10.120002090930939,39,768,149,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6793758101590058,none,True,1.0,-THoVjtIkeU__12.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-THoVjtIkeU__12.npz,e2f148f4f2e74724dc13b3ce870a0ce6a06740cfc224f5a9b3d0a870b196e7d4,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/12,-THoVjtIkeU,12,audio,14.896029472351074,14.592263090046677,1481,74,149,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9796075603322196,F0/HNR naturally undefined or unavailable in 790 rows; support masks retained,True,1.0,-THoVjtIkeU__12.npz::native_audio_*quality*,-THoVjtIkeU__12.npz::query_word_audio_H_time,content probe disabled,features_v2/-THoVjtIkeU__12.npz,e2f148f4f2e74724dc13b3ce870a0ce6a06740cfc224f5a9b3d0a870b196e7d4,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/12,-THoVjtIkeU,12,vision,14.896029472351074,14.816499710083006,445,35,149,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9946610093370394,none,True,1.0,-THoVjtIkeU__12.npz::native_vision_*quality*,-THoVjtIkeU__12.npz::query_word_vision_H_time,content probe disabled,features_v2/-THoVjtIkeU__12.npz,e2f148f4f2e74724dc13b3ce870a0ce6a06740cfc224f5a9b3d0a870b196e7d4,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/2,-THoVjtIkeU,2,text,4.2919921875,2.9200004227459426,10,768,43,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6803368447990547,none,True,1.0,-THoVjtIkeU__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-THoVjtIkeU__2.npz,35a6c2464ffd68dd96411edb5dc2763f0efb59f264c1d8b737e616fa6b0d1d5b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/2,-THoVjtIkeU,2,audio,4.2919921875,4.169859837841344,424,74,43,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9715441351421014,F0/HNR naturally undefined or unavailable in 226 rows; support masks retained,True,1.0,-THoVjtIkeU__2.npz::native_audio_*quality*,-THoVjtIkeU__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-THoVjtIkeU__2.npz,35a6c2464ffd68dd96411edb5dc2763f0efb59f264c1d8b737e616fa6b0d1d5b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/2,-THoVjtIkeU,2,vision,4.2919921875,4.249499797821044,128,35,43,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.990099611596985,none,True,1.0,-THoVjtIkeU__2.npz::native_vision_*quality*,-THoVjtIkeU__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-THoVjtIkeU__2.npz,35a6c2464ffd68dd96411edb5dc2763f0efb59f264c1d8b737e616fa6b0d1d5b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/6,-THoVjtIkeU,6,text,8.097004890441895,6.020000673830507,30,768,81,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7434848756157743,none,True,1.0,-THoVjtIkeU__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-THoVjtIkeU__6.npz,efaa55ee6394032c867046690a92c29edfd825005f0a090cca5ccb763b227f64,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/6,-THoVjtIkeU,6,audio,8.097004890441895,7.885292096717938,799,74,81,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9738529497525842,F0/HNR naturally undefined or unavailable in 361 rows; support masks retained,True,1.0,-THoVjtIkeU__6.npz::native_audio_*quality*,-THoVjtIkeU__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-THoVjtIkeU__6.npz,efaa55ee6394032c867046690a92c29edfd825005f0a090cca5ccb763b227f64,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/6,-THoVjtIkeU,6,vision,8.097004890441895,7.983000472187995,241,35,81,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9859201742130012,OpenFace confidence/success gate rejected 1 native frames,True,0.995850622406639,-THoVjtIkeU__6.npz::native_vision_*quality*,-THoVjtIkeU__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-THoVjtIkeU__6.npz,efaa55ee6394032c867046690a92c29edfd825005f0a090cca5ccb763b227f64,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/1,-UuX1xuaiiE,1,text,10.350000381469727,7.019999176263809,27,768,104,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6782607649785373,none,True,1.0,-UuX1xuaiiE__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-UuX1xuaiiE__1.npz,d5bbda38fcec15817d2b87bab5dcc559d6d425f7d28a82e6c32d13f14b48650c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/1,-UuX1xuaiiE,1,audio,10.350000381469727,10.122837774757594,1027,74,104,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9780519228657386,F0/HNR naturally undefined or unavailable in 535 rows; support masks retained,True,1.0,-UuX1xuaiiE__1.npz::native_audio_*quality*,-UuX1xuaiiE__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-UuX1xuaiiE__1.npz,d5bbda38fcec15817d2b87bab5dcc559d6d425f7d28a82e6c32d13f14b48650c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/1,-UuX1xuaiiE,1,vision,10.350000381469727,10.283500671386717,309,35,104,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9935749074751661,none,True,1.0,-UuX1xuaiiE__1.npz::native_vision_*quality*,-UuX1xuaiiE__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-UuX1xuaiiE__1.npz,d5bbda38fcec15817d2b87bab5dcc559d6d425f7d28a82e6c32d13f14b48650c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/0,-UuX1xuaiiE,0,text,3.6333329677581787,2.7200000882148743,9,768,37,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7486239528146399,none,True,1.0,-UuX1xuaiiE__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-UuX1xuaiiE__0.npz,f578b305d53bc152702f5e771de0fcd12d1e9aaefc5cefce3d2082a417aab7c8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/0,-UuX1xuaiiE,0,audio,3.6333329677581787,3.5266841811102796,358,74,37,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.970647119987546,F0/HNR naturally undefined or unavailable in 182 rows; support masks retained,True,1.0,-UuX1xuaiiE__0.npz::native_audio_*quality*,-UuX1xuaiiE__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-UuX1xuaiiE__0.npz,f578b305d53bc152702f5e771de0fcd12d1e9aaefc5cefce3d2082a417aab7c8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/0,-UuX1xuaiiE,0,vision,3.6333329677581787,3.6333329677581787,109,35,37,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-UuX1xuaiiE__0.npz::native_vision_*quality*,-UuX1xuaiiE__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-UuX1xuaiiE__0.npz,f578b305d53bc152702f5e771de0fcd12d1e9aaefc5cefce3d2082a417aab7c8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/3,-UuX1xuaiiE,3,text,4.1300129890441895,2.14000004529953,9,768,42,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5181581876319452,none,True,1.0,-UuX1xuaiiE__3.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-UuX1xuaiiE__3.npz,eddb408f25bdc13e6de2fb74caeed709cb7a8f00640e7897135330437f66a2aa,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/3,-UuX1xuaiiE,3,audio,4.1300129890441895,3.973609695966179,404,74,42,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9621300723525795,F0/HNR naturally undefined or unavailable in 240 rows; support masks retained,True,1.0,-UuX1xuaiiE__3.npz::native_audio_*quality*,-UuX1xuaiiE__3.npz::query_word_audio_H_time,content probe disabled,features_v2/-UuX1xuaiiE__3.npz,eddb408f25bdc13e6de2fb74caeed709cb7a8f00640e7897135330437f66a2aa,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/3,-UuX1xuaiiE,3,vision,4.1300129890441895,4.083499908447266,123,35,42,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9887377883022862,none,True,1.0,-UuX1xuaiiE__3.npz::native_vision_*quality*,-UuX1xuaiiE__3.npz::query_word_vision_H_time,content probe disabled,features_v2/-UuX1xuaiiE__3.npz,eddb408f25bdc13e6de2fb74caeed709cb7a8f00640e7897135330437f66a2aa,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/6,-UuX1xuaiiE,6,text,8.113997459411621,5.540000081062315,22,768,82,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6827707438626734,none,True,1.0,-UuX1xuaiiE__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-UuX1xuaiiE__6.npz,f0131ac00e44410fbd32c547a0d421c2791172434fc0203bb969abe14a530532,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/6,-UuX1xuaiiE,6,audio,8.113997459411621,7.830245652714294,795,74,82,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9650293448923629,F0/HNR naturally undefined or unavailable in 420 rows; support masks retained,True,1.0,-UuX1xuaiiE__6.npz::native_audio_*quality*,-UuX1xuaiiE__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-UuX1xuaiiE__6.npz,f0131ac00e44410fbd32c547a0d421c2791172434fc0203bb969abe14a530532,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/6,-UuX1xuaiiE,6,vision,8.113997459411621,7.983500003814696,240,35,82,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9839169957533623,none,True,1.0,-UuX1xuaiiE__6.npz::native_vision_*quality*,-UuX1xuaiiE__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-UuX1xuaiiE__6.npz,f0131ac00e44410fbd32c547a0d421c2791172434fc0203bb969abe14a530532,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-a55Q6RWvTA/3,-a55Q6RWvTA,3,text,22.15397071838379,16.560002624988556,65,768,222,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7474959155401767,none,True,1.0,-a55Q6RWvTA__3.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-a55Q6RWvTA__3.npz,15d029fc15f50b268b98f1e8abc65e45582e638c13a018d7aa74a18373275946,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-a55Q6RWvTA/3,-a55Q6RWvTA,3,audio,22.15397071838379,21.864563874094873,2209,74,222,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9869365700637692,F0/HNR naturally undefined or unavailable in 810 rows; support masks retained,True,1.0,-a55Q6RWvTA__3.npz::native_audio_*quality*,-a55Q6RWvTA__3.npz::query_word_audio_H_time,content probe disabled,features_v2/-a55Q6RWvTA__3.npz,15d029fc15f50b268b98f1e8abc65e45582e638c13a018d7aa74a18373275946,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-a55Q6RWvTA/3,-a55Q6RWvTA,3,vision,22.15397071838379,22.116500854492195,664,35,222,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9983086614870128,none,True,1.0,-a55Q6RWvTA__3.npz::native_vision_*quality*,-a55Q6RWvTA__3.npz::query_word_vision_H_time,content probe disabled,features_v2/-a55Q6RWvTA__3.npz,15d029fc15f50b268b98f1e8abc65e45582e638c13a018d7aa74a18373275946,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aNfi7CP8vM/7,-aNfi7CP8vM,7,text,8.694987297058105,6.360000669956207,18,768,87,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7314560047842823,none,True,1.0,-aNfi7CP8vM__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-aNfi7CP8vM__7.npz,0b6389f45bb966113c65a11998c6e7facdd24c8c42acf2e8cf32e0f2139402e1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aNfi7CP8vM/7,-aNfi7CP8vM,7,audio,8.694987297058105,8.439774326376012,854,74,87,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9706482641132274,F0/HNR naturally undefined or unavailable in 362 rows; support masks retained,True,1.0,-aNfi7CP8vM__7.npz::native_audio_*quality*,-aNfi7CP8vM__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-aNfi7CP8vM__7.npz,0b6389f45bb966113c65a11998c6e7facdd24c8c42acf2e8cf32e0f2139402e1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aNfi7CP8vM/7,-aNfi7CP8vM,7,vision,8.694987297058105,8.549499511718754,257,35,87,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.983267625314579,none,True,1.0,-aNfi7CP8vM__7.npz::native_vision_*quality*,-aNfi7CP8vM__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-aNfi7CP8vM__7.npz,0b6389f45bb966113c65a11998c6e7facdd24c8c42acf2e8cf32e0f2139402e1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aqamKhZ1Ec/0,-aqamKhZ1Ec,0,text,10.966667175292969,6.199999362230302,14,768,110,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5653494596971456,1 word intervals are not reliable under the fixed-transcript CTC path,True,0.9285714285714286,-aqamKhZ1Ec__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-aqamKhZ1Ec__0.npz,6f6e674bbba4353399e9e7825217f37e6d8ca1adf9675f33cf37106f31c55548,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aqamKhZ1Ec/0,-aqamKhZ1Ec,0,audio,10.966667175292969,10.701902656941803,1090,74,110,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9758573398718929,F0/HNR naturally undefined or unavailable in 697 rows; support masks retained,True,1.0,-aqamKhZ1Ec__0.npz::native_audio_*quality*,-aqamKhZ1Ec__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-aqamKhZ1Ec__0.npz,6f6e674bbba4353399e9e7825217f37e6d8ca1adf9675f33cf37106f31c55548,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aqamKhZ1Ec/0,-aqamKhZ1Ec,0,vision,10.966667175292969,10.966667175292969,329,35,110,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-aqamKhZ1Ec__0.npz::native_vision_*quality*,-aqamKhZ1Ec__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-aqamKhZ1Ec__0.npz,6f6e674bbba4353399e9e7825217f37e6d8ca1adf9675f33cf37106f31c55548,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/1,-dxfTGcXJoc,1,text,16.16100311279297,11.500000417232513,36,768,162,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.711589518111606,none,True,1.0,-dxfTGcXJoc__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-dxfTGcXJoc__1.npz,b5ffdc98a4a98b8f0aae55dee5fed66a43bcb129f47a250a004c1cc74e572595,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/1,-dxfTGcXJoc,1,audio,16.16100311279297,15.763708187116158,1602,74,162,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9754164439605661,F0/HNR naturally undefined or unavailable in 941 rows; support masks retained,True,1.0,-dxfTGcXJoc__1.npz::native_audio_*quality*,-dxfTGcXJoc__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-dxfTGcXJoc__1.npz,b5ffdc98a4a98b8f0aae55dee5fed66a43bcb129f47a250a004c1cc74e572595,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/1,-dxfTGcXJoc,1,vision,16.16100311279297,16.049501419067383,482,35,162,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9931005709888564,none,True,1.0,-dxfTGcXJoc__1.npz::native_vision_*quality*,-dxfTGcXJoc__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-dxfTGcXJoc__1.npz,b5ffdc98a4a98b8f0aae55dee5fed66a43bcb129f47a250a004c1cc74e572595,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/0,-dxfTGcXJoc,0,text,20.5,15.460005044937132,41,768,205,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7541465875579089,3 word intervals are not reliable under the fixed-transcript CTC path,True,0.926829268292683,-dxfTGcXJoc__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-dxfTGcXJoc__0.npz,04bd0be907fb46c613f926fe1b06bc2c3b30881c293156510baa34a4105ade55,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/0,-dxfTGcXJoc,0,audio,20.5,20.095867107043393,2042,74,205,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9802862003435802,F0/HNR naturally undefined or unavailable in 1164 rows; support masks retained,True,1.0,-dxfTGcXJoc__0.npz::native_audio_*quality*,-dxfTGcXJoc__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-dxfTGcXJoc__0.npz,04bd0be907fb46c613f926fe1b06bc2c3b30881c293156510baa34a4105ade55,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/0,-dxfTGcXJoc,0,vision,20.5,15.716499805450438,615,35,205,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.7666585270951433,OpenFace confidence/success gate rejected 144 native frames,True,0.7658536585365854,-dxfTGcXJoc__0.npz::native_vision_*quality*,-dxfTGcXJoc__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-dxfTGcXJoc__0.npz,04bd0be907fb46c613f926fe1b06bc2c3b30881c293156510baa34a4105ade55,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/2,-dxfTGcXJoc,2,text,16.697982788085938,12.100000262260437,34,768,167,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.724638443806148,2 word intervals are not reliable under the fixed-transcript CTC path,True,0.9411764705882353,-dxfTGcXJoc__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-dxfTGcXJoc__2.npz,46a5e523b5dd00364c99a3bcbc6fae4c80e1382a84b5b5e681c46eb074ed10f7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/2,-dxfTGcXJoc,2,audio,16.697982788085938,16.37964679743792,1664,74,167,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9809356618288558,F0/HNR naturally undefined or unavailable in 938 rows; support masks retained,True,1.0,-dxfTGcXJoc__2.npz::native_audio_*quality*,-dxfTGcXJoc__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-dxfTGcXJoc__2.npz,46a5e523b5dd00364c99a3bcbc6fae4c80e1382a84b5b5e681c46eb074ed10f7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/2,-dxfTGcXJoc,2,vision,16.697982788085938,16.649499893188473,500,35,167,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9970964819216332,none,True,1.0,-dxfTGcXJoc__2.npz::native_vision_*quality*,-dxfTGcXJoc__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-dxfTGcXJoc__2.npz,46a5e523b5dd00364c99a3bcbc6fae4c80e1382a84b5b5e681c46eb074ed10f7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/6,-dxfTGcXJoc,6,text,12.735026359558105,9.620002269744873,27,768,128,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7553971227177483,none,True,1.0,-dxfTGcXJoc__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-dxfTGcXJoc__6.npz,55c1ff86729ab21da743a5107ed07b220e41b4193cd1c0ba9f009e65c3ab0513,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/6,-dxfTGcXJoc,6,audio,12.735026359558105,12.405358507826525,1263,74,128,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9741132964766773,F0/HNR naturally undefined or unavailable in 825 rows; support masks retained,True,1.0,-dxfTGcXJoc__6.npz::native_audio_*quality*,-dxfTGcXJoc__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-dxfTGcXJoc__6.npz,55c1ff86729ab21da743a5107ed07b220e41b4193cd1c0ba9f009e65c3ab0513,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/6,-dxfTGcXJoc,6,vision,12.735026359558105,12.683500289916996,381,35,128,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9959539879866494,none,True,1.0,-dxfTGcXJoc__6.npz::native_vision_*quality*,-dxfTGcXJoc__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-dxfTGcXJoc__6.npz,55c1ff86729ab21da743a5107ed07b220e41b4193cd1c0ba9f009e65c3ab0513,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/26,-egA8-b7-3M,26,text,6.030990123748779,4.419999480247498,15,768,61,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7328812333554424,none,True,1.0,-egA8-b7-3M__26.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__26.npz,11f78aed680ea9c5862a15e04ff6c1f8776c46d29c98b68d28773ff39d959685,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/26,-egA8-b7-3M,26,audio,6.030990123748779,5.863275968545191,593,74,61,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9721912734456047,F0/HNR naturally undefined or unavailable in 213 rows; support masks retained,True,1.0,-egA8-b7-3M__26.npz::native_audio_*quality*,-egA8-b7-3M__26.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__26.npz,11f78aed680ea9c5862a15e04ff6c1f8776c46d29c98b68d28773ff39d959685,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/26,-egA8-b7-3M,26,vision,6.030990123748779,5.949500083923338,179,35,61,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9864881158560432,none,True,1.0,-egA8-b7-3M__26.npz::native_vision_*quality*,-egA8-b7-3M__26.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__26.npz,11f78aed680ea9c5862a15e04ff6c1f8776c46d29c98b68d28773ff39d959685,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/17,-egA8-b7-3M,17,text,7.271028995513916,3.720000565052031,16,768,73,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5116195475698418,none,True,1.0,-egA8-b7-3M__17.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__17.npz,f967e81e0edd51c3700671721afb3fbecbbc32652f27d2db40ea444e8d80c68f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/17,-egA8-b7-3M,17,audio,7.271028995513916,7.009598084398218,710,74,73,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9640448537233182,F0/HNR naturally undefined or unavailable in 317 rows; support masks retained,True,1.0,-egA8-b7-3M__17.npz::native_audio_*quality*,-egA8-b7-3M__17.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__17.npz,f967e81e0edd51c3700671721afb3fbecbbc32652f27d2db40ea444e8d80c68f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/17,-egA8-b7-3M,17,vision,7.271028995513916,7.149499893188478,215,35,73,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9832858454559295,none,True,1.0,-egA8-b7-3M__17.npz::native_vision_*quality*,-egA8-b7-3M__17.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__17.npz,f967e81e0edd51c3700671721afb3fbecbbc32652f27d2db40ea444e8d80c68f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/18,-egA8-b7-3M,18,text,8.386002540588379,5.15999972820282,22,768,84,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6153110141844512,none,True,1.0,-egA8-b7-3M__18.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__18.npz,fd4bc95a6adfb16a9588b7ed65cc2812186ce3d607804dc76f4486312951ef90,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/18,-egA8-b7-3M,18,audio,8.386002540588379,8.171099232660758,827,74,84,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9743735698996648,F0/HNR naturally undefined or unavailable in 350 rows; support masks retained,True,1.0,-egA8-b7-3M__18.npz::native_audio_*quality*,-egA8-b7-3M__18.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__18.npz,fd4bc95a6adfb16a9588b7ed65cc2812186ce3d607804dc76f4486312951ef90,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/18,-egA8-b7-3M,18,vision,8.386002540588379,8.316499710083008,250,35,84,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9917120427558928,none,True,1.0,-egA8-b7-3M__18.npz::native_vision_*quality*,-egA8-b7-3M__18.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__18.npz,fd4bc95a6adfb16a9588b7ed65cc2812186ce3d607804dc76f4486312951ef90,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/16,-egA8-b7-3M,16,text,5.538021087646484,3.759999781847,11,768,56,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6789428430011455,none,True,1.0,-egA8-b7-3M__16.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__16.npz,4c015f85e3bd901ecedde8c0c2ca4a756d9a77dfe4177625c481581f2037ff9c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/16,-egA8-b7-3M,16,audio,5.538021087646484,5.426222804430368,549,74,56,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9798125934432604,F0/HNR naturally undefined or unavailable in 210 rows; support masks retained,True,1.0,-egA8-b7-3M__16.npz::native_audio_*quality*,-egA8-b7-3M__16.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__16.npz,4c015f85e3bd901ecedde8c0c2ca4a756d9a77dfe4177625c481581f2037ff9c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/16,-egA8-b7-3M,16,vision,5.538021087646484,5.538021087646484,166,35,56,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-egA8-b7-3M__16.npz::native_vision_*quality*,-egA8-b7-3M__16.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__16.npz,4c015f85e3bd901ecedde8c0c2ca4a756d9a77dfe4177625c481581f2037ff9c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/13,-egA8-b7-3M,13,text,4.18398380279541,2.9599998593330383,11,768,42,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.707459684082763,none,True,1.0,-egA8-b7-3M__13.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__13.npz,cbd627c225ebca37521ae238cb9ec254cad236822f4f37ebfbb039e6c56aea4c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/13,-egA8-b7-3M,13,audio,4.18398380279541,3.9740897545943383,403,74,42,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9498339242946311,F0/HNR naturally undefined or unavailable in 184 rows; support masks retained,True,1.0,-egA8-b7-3M__13.npz::native_audio_*quality*,-egA8-b7-3M__13.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__13.npz,cbd627c225ebca37521ae238cb9ec254cad236822f4f37ebfbb039e6c56aea4c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/13,-egA8-b7-3M,13,vision,4.18398380279541,4.0834999084472665,123,35,42,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9759836798887682,none,True,1.0,-egA8-b7-3M__13.npz::native_vision_*quality*,-egA8-b7-3M__13.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__13.npz,cbd627c225ebca37521ae238cb9ec254cad236822f4f37ebfbb039e6c56aea4c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/1,-egA8-b7-3M,1,text,10.266016006469727,6.960000624880197,20,768,103,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6779651054989538,1 word intervals are not reliable under the fixed-transcript CTC path,True,0.95,-egA8-b7-3M__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__1.npz,589a7989b1b4888568c31614a2d6beefba88fb87289c85b94df2d8051500403b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/1,-egA8-b7-3M,1,audio,10.266016006469727,10.060219577840856,1016,74,103,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.979953622856308,F0/HNR naturally undefined or unavailable in 356 rows; support masks retained,True,1.0,-egA8-b7-3M__1.npz::native_audio_*quality*,-egA8-b7-3M__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__1.npz,589a7989b1b4888568c31614a2d6beefba88fb87289c85b94df2d8051500403b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/1,-egA8-b7-3M,1,vision,10.266016006469727,10.183500289916992,306,35,103,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9919622454805513,none,True,1.0,-egA8-b7-3M__1.npz::native_vision_*quality*,-egA8-b7-3M__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__1.npz,589a7989b1b4888568c31614a2d6beefba88fb87289c85b94df2d8051500403b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/6,-egA8-b7-3M,6,text,6.264974117279053,4.020000159740448,12,768,63,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6416626923730019,none,True,1.0,-egA8-b7-3M__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__6.npz,2e88a00d5e55863aa956b4b0949fc0e4f2975ccfd83b3194957f6fca86ecfce0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/6,-egA8-b7-3M,6,audio,6.264974117279053,6.125261839982626,619,74,63,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.977699464565848,F0/HNR naturally undefined or unavailable in 214 rows; support masks retained,True,1.0,-egA8-b7-3M__6.npz::native_audio_*quality*,-egA8-b7-3M__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__6.npz,2e88a00d5e55863aa956b4b0949fc0e4f2975ccfd83b3194957f6fca86ecfce0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/6,-egA8-b7-3M,6,vision,6.264974117279053,6.2164998054504395,187,35,63,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9922626477107193,none,True,1.0,-egA8-b7-3M__6.npz::native_vision_*quality*,-egA8-b7-3M__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__6.npz,2e88a00d5e55863aa956b4b0949fc0e4f2975ccfd83b3194957f6fca86ecfce0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/9,-egA8-b7-3M,9,text,5.0899739265441895,3.099999785423278,10,768,51,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6090404057389752,none,True,1.0,-egA8-b7-3M__9.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__9.npz,bcd643f328616312eae9b28e6c9aabbe8acaec3e4c4be58daeb45e3eee4d973c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/9,-egA8-b7-3M,9,audio,5.0899739265441895,4.889356205592284,494,74,51,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9605857075405269,F0/HNR naturally undefined or unavailable in 173 rows; support masks retained,True,1.0,-egA8-b7-3M__9.npz::native_audio_*quality*,-egA8-b7-3M__9.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__9.npz,bcd643f328616312eae9b28e6c9aabbe8acaec3e4c4be58daeb45e3eee4d973c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/9,-egA8-b7-3M,9,vision,5.0899739265441895,4.983500003814698,150,35,51,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.979081636906973,none,True,1.0,-egA8-b7-3M__9.npz::native_vision_*quality*,-egA8-b7-3M__9.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__9.npz,bcd643f328616312eae9b28e6c9aabbe8acaec3e4c4be58daeb45e3eee4d973c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/20,-egA8-b7-3M,20,text,6.644987106323242,4.5400003790855425,20,768,67,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6832218492591754,none,True,1.0,-egA8-b7-3M__20.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-egA8-b7-3M__20.npz,719ef133920e74b3ea33925d2bca0270b062199116867568f88ac83cadcd74e8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/20,-egA8-b7-3M,20,audio,6.644987106323242,6.393548356520164,648,74,67,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9621611380458791,F0/HNR naturally undefined or unavailable in 299 rows; support masks retained,True,1.0,-egA8-b7-3M__20.npz::native_audio_*quality*,-egA8-b7-3M__20.npz::query_word_audio_H_time,content probe disabled,features_v2/-egA8-b7-3M__20.npz,719ef133920e74b3ea33925d2bca0270b062199116867568f88ac83cadcd74e8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/20,-egA8-b7-3M,20,vision,6.644987106323242,6.5164999961853045,196,35,67,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9806640542589358,none,True,1.0,-egA8-b7-3M__20.npz::native_vision_*quality*,-egA8-b7-3M__20.npz::query_word_vision_H_time,content probe disabled,features_v2/-egA8-b7-3M__20.npz,719ef133920e74b3ea33925d2bca0270b062199116867568f88ac83cadcd74e8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/3,-iRBcNs9oI8,3,text,5.620999813079834,2.180000007152557,8,768,57,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.3878313609048318,none,True,1.0,-iRBcNs9oI8__3.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-iRBcNs9oI8__3.npz,39c547acd1a8da2ebc6c6ab008191a5676420398a974cd9611cad087f0ccec50,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/3,-iRBcNs9oI8,3,audio,5.620999813079834,5.476260099862072,554,74,57,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9742501835917237,F0/HNR naturally undefined or unavailable in 229 rows; support masks retained,True,1.0,-iRBcNs9oI8__3.npz::native_audio_*quality*,-iRBcNs9oI8__3.npz::query_word_audio_H_time,content probe disabled,features_v2/-iRBcNs9oI8__3.npz,39c547acd1a8da2ebc6c6ab008191a5676420398a974cd9611cad087f0ccec50,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/3,-iRBcNs9oI8,3,vision,5.620999813079834,5.555500030517579,167,35,57,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9883473074647966,none,True,1.0,-iRBcNs9oI8__3.npz::native_vision_*quality*,-iRBcNs9oI8__3.npz::query_word_vision_H_time,content probe disabled,features_v2/-iRBcNs9oI8__3.npz,39c547acd1a8da2ebc6c6ab008191a5676420398a974cd9611cad087f0ccec50,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/7,-iRBcNs9oI8,7,text,4.104000091552734,2.559999972581863,9,768,42,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6237816558169949,none,True,1.0,-iRBcNs9oI8__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-iRBcNs9oI8__7.npz,cc7b1c7a06ec41d72007b5c42fb8036f30b66d5cf76e3f093160c22fc5e38081,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/7,-iRBcNs9oI8,7,audio,4.104000091552734,3.955483294822074,401,74,42,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9638116975103503,F0/HNR naturally undefined or unavailable in 186 rows; support masks retained,True,1.0,-iRBcNs9oI8__7.npz::native_audio_*quality*,-iRBcNs9oI8__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-iRBcNs9oI8__7.npz,cc7b1c7a06ec41d72007b5c42fb8036f30b66d5cf76e3f093160c22fc5e38081,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/7,-iRBcNs9oI8,7,vision,4.104000091552734,3.9874999523162833,120,35,42,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9716130271351009,none,True,1.0,-iRBcNs9oI8__7.npz::native_vision_*quality*,-iRBcNs9oI8__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-iRBcNs9oI8__7.npz,cc7b1c7a06ec41d72007b5c42fb8036f30b66d5cf76e3f093160c22fc5e38081,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/6,-iRBcNs9oI8,6,text,2.9030001163482666,1.5799999386072159,5,768,30,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5442645109483236,none,True,1.0,-iRBcNs9oI8__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-iRBcNs9oI8__6.npz,030b0b8d8d4ae47799432ab71b66fcc881f077c5b0feef892c1f146dc0a592e3,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/6,-iRBcNs9oI8,6,audio,2.9030001163482666,2.7904534790966964,283,74,30,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9612309222387684,F0/HNR naturally undefined or unavailable in 112 rows; support masks retained,True,1.0,-iRBcNs9oI8__6.npz::native_audio_*quality*,-iRBcNs9oI8__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-iRBcNs9oI8__6.npz,030b0b8d8d4ae47799432ab71b66fcc881f077c5b0feef892c1f146dc0a592e3,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/6,-iRBcNs9oI8,6,vision,2.9030001163482666,2.819499969482422,85,35,30,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9712366023013183,none,True,1.0,-iRBcNs9oI8__6.npz::native_vision_*quality*,-iRBcNs9oI8__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-iRBcNs9oI8__6.npz,030b0b8d8d4ae47799432ab71b66fcc881f077c5b0feef892c1f146dc0a592e3,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/9,-iRBcNs9oI8,9,text,3.4159998893737793,1.4199999868869781,5,768,35,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.41569087613386674,none,True,1.0,-iRBcNs9oI8__9.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-iRBcNs9oI8__9.npz,352bdcc73d3622d6abac1b09f2039b211d15b9bf7acee66f0b07783c9b7a4c74,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/9,-iRBcNs9oI8,9,audio,3.4159998893737793,3.3255532545012394,338,74,35,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9735226470135745,F0/HNR naturally undefined or unavailable in 180 rows; support masks retained,True,1.0,-iRBcNs9oI8__9.npz::native_audio_*quality*,-iRBcNs9oI8__9.npz::query_word_audio_H_time,content probe disabled,features_v2/-iRBcNs9oI8__9.npz,352bdcc73d3622d6abac1b09f2039b211d15b9bf7acee66f0b07783c9b7a4c74,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/9,-iRBcNs9oI8,9,vision,3.4159998893737793,3.049999833106995,102,35,35,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.8928571229158092,OpenFace confidence/success gate rejected 11 native frames,True,0.8921568627450981,-iRBcNs9oI8__9.npz::native_vision_*quality*,-iRBcNs9oI8__9.npz::query_word_vision_H_time,content probe disabled,features_v2/-iRBcNs9oI8__9.npz,352bdcc73d3622d6abac1b09f2039b211d15b9bf7acee66f0b07783c9b7a4c74,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/8,-iRBcNs9oI8,8,text,8.093000411987305,4.739999324083329,21,768,81,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5856912248592586,none,True,1.0,-iRBcNs9oI8__8.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-iRBcNs9oI8__8.npz,72a49e3da2862addcde033bd2fbae957d086533664ca4dcbccd3a851bcd50412,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/8,-iRBcNs9oI8,8,audio,8.093000411987305,7.9095671503125,803,74,81,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9773343318501375,F0/HNR naturally undefined or unavailable in 431 rows; support masks retained,True,1.0,-iRBcNs9oI8__8.npz::native_audio_*quality*,-iRBcNs9oI8__8.npz::query_word_audio_H_time,content probe disabled,features_v2/-iRBcNs9oI8__8.npz,72a49e3da2862addcde033bd2fbae957d086533664ca4dcbccd3a851bcd50412,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/8,-iRBcNs9oI8,8,vision,8.093000411987305,8.024499893188477,241,35,81,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9915358315443348,none,True,1.0,-iRBcNs9oI8__8.npz::native_vision_*quality*,-iRBcNs9oI8__8.npz::query_word_vision_H_time,content probe disabled,features_v2/-iRBcNs9oI8__8.npz,72a49e3da2862addcde033bd2fbae957d086533664ca4dcbccd3a851bcd50412,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/5,-lzEya4AM_4,5,text,7.675000190734863,5.020000159740448,19,768,77,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6540716657962448,none,True,1.0,-lzEya4AM_4__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-lzEya4AM_4__5.npz,3b74de8d0e66fd754594f05985e5af3480adcc03555f79225d3b727cb4bed043,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/5,-lzEya4AM_4,5,audio,7.675000190734863,7.46790949396185,757,74,77,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.973017499462344,F0/HNR naturally undefined or unavailable in 376 rows; support masks retained,True,1.0,-lzEya4AM_4__5.npz::native_audio_*quality*,-lzEya4AM_4__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-lzEya4AM_4__5.npz,3b74de8d0e66fd754594f05985e5af3480adcc03555f79225d3b727cb4bed043,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/5,-lzEya4AM_4,5,vision,7.675000190734863,7.616499900817873,229,35,77,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9923778125780881,none,True,1.0,-lzEya4AM_4__5.npz::native_vision_*quality*,-lzEya4AM_4__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-lzEya4AM_4__5.npz,3b74de8d0e66fd754594f05985e5af3480adcc03555f79225d3b727cb4bed043,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/6,-lzEya4AM_4,6,text,14.7919921875,10.32000094652176,43,768,148,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6976748510753471,none,True,1.0,-lzEya4AM_4__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-lzEya4AM_4__6.npz,ff5da5afb551001d58100ed2471caea51c741046d5396957efbf40082307ca18,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/6,-lzEya4AM_4,6,audio,14.7919921875,14.509920233004806,1475,74,148,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9809307663957827,F0/HNR naturally undefined or unavailable in 853 rows; support masks retained,True,1.0,-lzEya4AM_4__6.npz::native_audio_*quality*,-lzEya4AM_4__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-lzEya4AM_4__6.npz,ff5da5afb551001d58100ed2471caea51c741046d5396957efbf40082307ca18,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/6,-lzEya4AM_4,6,vision,14.7919921875,14.683000266551971,443,35,148,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9926316942595378,OpenFace confidence/success gate rejected 2 native frames,True,1.0,-lzEya4AM_4__6.npz::native_vision_*quality*,-lzEya4AM_4__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-lzEya4AM_4__6.npz,ff5da5afb551001d58100ed2471caea51c741046d5396957efbf40082307ca18,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/1,-mJ2ud6oKI8,1,text,6.103000164031982,1.9600001871585846,13,768,62,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.3211535530852254,none,True,1.0,-mJ2ud6oKI8__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-mJ2ud6oKI8__1.npz,c8e3acb4ca08679796c4c8cdfcd8c2b3cffc0465015efaffa1eccdb55ff4f45b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/1,-mJ2ud6oKI8,1,audio,6.103000164031982,5.890635232667666,606,74,62,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9652031909460058,F0/HNR naturally undefined or unavailable in 606 rows; support masks retained,True,1.0,-mJ2ud6oKI8__1.npz::native_audio_*quality*,-mJ2ud6oKI8__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__1.npz,c8e3acb4ca08679796c4c8cdfcd8c2b3cffc0465015efaffa1eccdb55ff4f45b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/1,-mJ2ud6oKI8,1,vision,6.103000164031982,0.0,182,35,62,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.0,OpenFace confidence/success gate rejected 182 native frames,False,0.0,-mJ2ud6oKI8__1.npz::native_vision_*quality*,-mJ2ud6oKI8__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__1.npz,c8e3acb4ca08679796c4c8cdfcd8c2b3cffc0465015efaffa1eccdb55ff4f45b,failed_or_unavailable,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/2,-mJ2ud6oKI8,2,text,5.730999946594238,3.5200002752244473,11,768,58,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6142035086418519,none,True,1.0,-mJ2ud6oKI8__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-mJ2ud6oKI8__2.npz,c41790f8b75f886bd1f5d6d72cbab1985306d0b715e5ae47203f3d0c8a3e7b28,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/2,-mJ2ud6oKI8,2,audio,5.730999946594238,5.547395957482827,571,74,58,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.967963009802413,F0/HNR naturally undefined or unavailable in 571 rows; support masks retained,True,1.0,-mJ2ud6oKI8__2.npz::native_audio_*quality*,-mJ2ud6oKI8__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__2.npz,c41790f8b75f886bd1f5d6d72cbab1985306d0b715e5ae47203f3d0c8a3e7b28,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/2,-mJ2ud6oKI8,2,vision,5.730999946594238,2.4679999351501465,171,35,58,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.4306403695949788,OpenFace confidence/success gate rejected 97 native frames,True,0.6491228070175439,-mJ2ud6oKI8__2.npz::native_vision_*quality*,-mJ2ud6oKI8__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__2.npz,c41790f8b75f886bd1f5d6d72cbab1985306d0b715e5ae47203f3d0c8a3e7b28,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/6,-mJ2ud6oKI8,6,text,2.256999969482422,1.4199999272823334,5,768,23,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6291537201960927,none,True,1.0,-mJ2ud6oKI8__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-mJ2ud6oKI8__6.npz,134a3a4ebbc423760133b0da0e90cfe84d75ea85df3968000afd84153c5d828d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/6,-mJ2ud6oKI8,6,audio,2.256999969482422,2.1964105617355654,223,74,23,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9731548920841364,F0/HNR naturally undefined or unavailable in 117 rows; support masks retained,True,1.0,-mJ2ud6oKI8__6.npz::native_audio_*quality*,-mJ2ud6oKI8__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__6.npz,134a3a4ebbc423760133b0da0e90cfe84d75ea85df3968000afd84153c5d828d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/6,-mJ2ud6oKI8,6,vision,2.256999969482422,2.218499898910522,67,35,23,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9829419268531365,none,True,1.0,-mJ2ud6oKI8__6.npz::native_vision_*quality*,-mJ2ud6oKI8__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__6.npz,134a3a4ebbc423760133b0da0e90cfe84d75ea85df3968000afd84153c5d828d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/9,-mJ2ud6oKI8,9,text,7.0329999923706055,5.320001155138016,22,768,71,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7564341192818357,none,True,1.0,-mJ2ud6oKI8__9.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-mJ2ud6oKI8__9.npz,df3442f3ed8f894b507923e87e426ae9636790a4a502d00c350a215270cc3e47,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/9,-mJ2ud6oKI8,9,audio,7.0329999923706055,6.776402102732981,691,74,71,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9635151585502657,F0/HNR naturally undefined or unavailable in 490 rows; support masks retained,True,1.0,-mJ2ud6oKI8__9.npz::native_audio_*quality*,-mJ2ud6oKI8__9.npz::query_word_audio_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__9.npz,df3442f3ed8f894b507923e87e426ae9636790a4a502d00c350a215270cc3e47,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/9,-mJ2ud6oKI8,9,vision,7.0329999923706055,6.923500061035155,208,35,71,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9844305514781408,none,True,1.0,-mJ2ud6oKI8__9.npz::native_vision_*quality*,-mJ2ud6oKI8__9.npz::query_word_vision_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__9.npz,df3442f3ed8f894b507923e87e426ae9636790a4a502d00c350a215270cc3e47,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/8,-mJ2ud6oKI8,8,text,4.191999912261963,2.740000009536743,11,768,42,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6536259701537698,none,True,1.0,-mJ2ud6oKI8__8.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-mJ2ud6oKI8__8.npz,6af6614c9838417a48c4817f4dd5bf1e6669eb5f78f983e9a68adc113fcdc7cc,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/8,-mJ2ud6oKI8,8,audio,4.191999912261963,4.1114204123213485,418,74,42,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9807777906423822,F0/HNR naturally undefined or unavailable in 222 rows; support masks retained,True,1.0,-mJ2ud6oKI8__8.npz::native_audio_*quality*,-mJ2ud6oKI8__8.npz::query_word_audio_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__8.npz,6af6614c9838417a48c4817f4dd5bf1e6669eb5f78f983e9a68adc113fcdc7cc,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/8,-mJ2ud6oKI8,8,vision,4.191999912261963,4.153500080108643,125,35,42,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9908158795421954,none,True,1.0,-mJ2ud6oKI8__8.npz::native_vision_*quality*,-mJ2ud6oKI8__8.npz::query_word_vision_H_time,content probe disabled,features_v2/-mJ2ud6oKI8__8.npz,6af6614c9838417a48c4817f4dd5bf1e6669eb5f78f983e9a68adc113fcdc7cc,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mqbVkbCndg/0,-mqbVkbCndg,0,text,6.466667175292969,4.500000059604645,12,768,65,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6958762431438688,none,True,1.0,-mqbVkbCndg__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-mqbVkbCndg__0.npz,cdeba953bba30907814acf72f3e35582dec263eaa25bdd922dad7fafe02e4dda,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mqbVkbCndg/0,-mqbVkbCndg,0,audio,6.466667175292969,6.299567509341885,639,74,65,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9741598475039016,F0/HNR naturally undefined or unavailable in 318 rows; support masks retained,True,1.0,-mqbVkbCndg__0.npz::native_audio_*quality*,-mqbVkbCndg__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-mqbVkbCndg__0.npz,cdeba953bba30907814acf72f3e35582dec263eaa25bdd922dad7fafe02e4dda,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mqbVkbCndg/0,-mqbVkbCndg,0,vision,6.466667175292969,6.466667175292969,194,35,65,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-mqbVkbCndg__0.npz::native_vision_*quality*,-mqbVkbCndg__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-mqbVkbCndg__0.npz,cdeba953bba30907814acf72f3e35582dec263eaa25bdd922dad7fafe02e4dda,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/2,-t217m2on-s,2,text,5.6860032081604,3.020000100135804,12,768,57,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5311288069274355,none,True,1.0,-t217m2on-s__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-t217m2on-s__2.npz,6713a983112173204502dc5adbb886bc6c38554b4627698611931420c47663a8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/2,-t217m2on-s,2,audio,5.6860032081604,5.571092048206844,564,74,57,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9797905214354015,F0/HNR naturally undefined or unavailable in 219 rows; support masks retained,True,1.0,-t217m2on-s__2.npz::native_audio_*quality*,-t217m2on-s__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-t217m2on-s__2.npz,6713a983112173204502dc5adbb886bc6c38554b4627698611931420c47663a8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/2,-t217m2on-s,2,vision,5.6860032081604,5.649499893188477,170,35,57,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9935801452029547,none,True,1.0,-t217m2on-s__2.npz::native_vision_*quality*,-t217m2on-s__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-t217m2on-s__2.npz,6713a983112173204502dc5adbb886bc6c38554b4627698611931420c47663a8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/7,-t217m2on-s,7,text,17.183008193969727,12.199996888637546,45,768,172,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7100035541459534,none,True,1.0,-t217m2on-s__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-t217m2on-s__7.npz,41c76b8a733ecb780d56348f55d00f0ba0c07f44f46e93044e62d876710180b7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/7,-t217m2on-s,7,audio,17.183008193969727,16.794731244847583,1703,74,172,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9774034357232974,F0/HNR naturally undefined or unavailable in 857 rows; support masks retained,True,1.0,-t217m2on-s__7.npz::native_audio_*quality*,-t217m2on-s__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-t217m2on-s__7.npz,41c76b8a733ecb780d56348f55d00f0ba0c07f44f46e93044e62d876710180b7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/7,-t217m2on-s,7,vision,17.183008193969727,17.04950141906738,512,35,172,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9922303025526578,none,True,1.0,-t217m2on-s__7.npz::native_vision_*quality*,-t217m2on-s__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-t217m2on-s__7.npz,41c76b8a733ecb780d56348f55d00f0ba0c07f44f46e93044e62d876710180b7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tANM6ETl_M/3,-tANM6ETl_M,3,text,6.758008003234863,4.280000329017639,22,768,68,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6333227671480895,none,True,1.0,-tANM6ETl_M__3.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-tANM6ETl_M__3.npz,6d3af064c060dad3816a9e1dfa00101faebd8b7da7d8ceea85b0bc0fca70abd6,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tANM6ETl_M/3,-tANM6ETl_M,3,audio,6.758008003234863,6.533308992514736,661,74,68,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9667507036670318,F0/HNR naturally undefined or unavailable in 260 rows; support masks retained,True,1.0,-tANM6ETl_M__3.npz::native_audio_*quality*,-tANM6ETl_M__3.npz::query_word_audio_H_time,content probe disabled,features_v2/-tANM6ETl_M__3.npz,6d3af064c060dad3816a9e1dfa00101faebd8b7da7d8ceea85b0bc0fca70abd6,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tANM6ETl_M/3,-tANM6ETl_M,3,vision,6.758008003234863,6.616499900817871,200,35,68,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9790606784796265,OpenFace confidence/success gate rejected 1 native frames,True,1.0,-tANM6ETl_M__3.npz::native_vision_*quality*,-tANM6ETl_M__3.npz::query_word_vision_H_time,content probe disabled,features_v2/-tANM6ETl_M__3.npz,6d3af064c060dad3816a9e1dfa00101faebd8b7da7d8ceea85b0bc0fca70abd6,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/11,-tPCytz4rww,11,text,5.466991901397705,4.0599993616342545,17,768,55,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7426386273951247,none,True,1.0,-tPCytz4rww__11.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-tPCytz4rww__11.npz,6dc09d02467baaef67594ff32cffb01f8ed1565cda4c5abd161b8261a51e2e5d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/11,-tPCytz4rww,11,audio,5.466991901397705,5.319195072393161,538,74,55,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9729656030829755,F0/HNR naturally undefined or unavailable in 220 rows; support masks retained,True,1.0,-tPCytz4rww__11.npz::native_audio_*quality*,-tPCytz4rww__11.npz::query_word_audio_H_time,content probe disabled,features_v2/-tPCytz4rww__11.npz,6dc09d02467baaef67594ff32cffb01f8ed1565cda4c5abd161b8261a51e2e5d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/11,-tPCytz4rww,11,vision,5.466991901397705,5.466991901397705,164,35,55,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-tPCytz4rww__11.npz::native_vision_*quality*,-tPCytz4rww__11.npz::query_word_vision_H_time,content probe disabled,features_v2/-tPCytz4rww__11.npz,6dc09d02467baaef67594ff32cffb01f8ed1565cda4c5abd161b8261a51e2e5d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/10,-tPCytz4rww,10,text,4.788997173309326,3.279999792575836,12,768,48,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6849032634340162,none,True,1.0,-tPCytz4rww__10.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-tPCytz4rww__10.npz,758183192cbd5f0f20823d26ece3ddee40dec0aadec35247b2495510a53f52f8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/10,-tPCytz4rww,10,audio,4.788997173309326,4.579772797790734,468,74,48,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.956311443096131,F0/HNR naturally undefined or unavailable in 366 rows; support masks retained,True,1.0,-tPCytz4rww__10.npz::native_audio_*quality*,-tPCytz4rww__10.npz::query_word_audio_H_time,content probe disabled,features_v2/-tPCytz4rww__10.npz,758183192cbd5f0f20823d26ece3ddee40dec0aadec35247b2495510a53f52f8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/10,-tPCytz4rww,10,vision,4.788997173309326,4.71649980545044,142,35,48,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.984861680799701,none,True,1.0,-tPCytz4rww__10.npz::native_vision_*quality*,-tPCytz4rww__10.npz::query_word_vision_H_time,content probe disabled,features_v2/-tPCytz4rww__10.npz,758183192cbd5f0f20823d26ece3ddee40dec0aadec35247b2495510a53f52f8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/12,-tPCytz4rww,12,text,11.863997459411621,7.539998061954974,26,768,119,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6355360482628517,none,True,1.0,-tPCytz4rww__12.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-tPCytz4rww__12.npz,e3c7f9cd67ad2d20fef997696dc278361f97f027db58762e22104e5720d509d5,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/12,-tPCytz4rww,12,audio,11.863997459411621,11.566984429552749,1174,74,119,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9749651809286882,F0/HNR naturally undefined or unavailable in 626 rows; support masks retained,True,1.0,-tPCytz4rww__12.npz::native_audio_*quality*,-tPCytz4rww__12.npz::query_word_audio_H_time,content probe disabled,features_v2/-tPCytz4rww__12.npz,e3c7f9cd67ad2d20fef997696dc278361f97f027db58762e22104e5720d509d5,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/12,-tPCytz4rww,12,vision,11.863997459411621,11.749500274658205,353,35,119,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9903491900478631,none,True,1.0,-tPCytz4rww__12.npz::native_vision_*quality*,-tPCytz4rww__12.npz::query_word_vision_H_time,content probe disabled,features_v2/-tPCytz4rww__12.npz,e3c7f9cd67ad2d20fef997696dc278361f97f027db58762e22104e5720d509d5,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/16,-tPCytz4rww,16,text,7.205989837646484,3.3600009679794303,16,768,73,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.46627889348742474,none,True,1.0,-tPCytz4rww__16.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-tPCytz4rww__16.npz,8b3f82f9628792eebaf8c227becc2ebeba59bb8abb668bcca86e07fb1a992a93,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/16,-tPCytz4rww,16,audio,7.205989837646484,7.033848639275576,714,74,73,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9761113736975335,F0/HNR naturally undefined or unavailable in 390 rows; support masks retained,True,1.0,-tPCytz4rww__16.npz::native_audio_*quality*,-tPCytz4rww__16.npz::query_word_audio_H_time,content probe disabled,features_v2/-tPCytz4rww__16.npz,8b3f82f9628792eebaf8c227becc2ebeba59bb8abb668bcca86e07fb1a992a93,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/16,-tPCytz4rww,16,vision,7.205989837646484,7.205989837646484,216,35,73,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-tPCytz4rww__16.npz::native_vision_*quality*,-tPCytz4rww__16.npz::query_word_vision_H_time,content probe disabled,features_v2/-tPCytz4rww__16.npz,8b3f82f9628792eebaf8c227becc2ebeba59bb8abb668bcca86e07fb1a992a93,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/18,-tPCytz4rww,18,text,6.644987106323242,3.960000157356262,16,768,67,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5959379745956169,none,True,1.0,-tPCytz4rww__18.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-tPCytz4rww__18.npz,eece70451e9c37a4b5f375bf646f66e09113fe3467f35bf1720cc14d986c1776,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/18,-tPCytz4rww,18,audio,6.644987106323242,6.500515924112216,657,74,67,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9782586211380982,F0/HNR naturally undefined or unavailable in 253 rows; support masks retained,True,1.0,-tPCytz4rww__18.npz::native_audio_*quality*,-tPCytz4rww__18.npz::query_word_audio_H_time,content probe disabled,features_v2/-tPCytz4rww__18.npz,eece70451e9c37a4b5f375bf646f66e09113fe3467f35bf1720cc14d986c1776,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/18,-tPCytz4rww,18,vision,6.644987106323242,6.644987106323242,199,35,67,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-tPCytz4rww__18.npz::native_vision_*quality*,-tPCytz4rww__18.npz::query_word_vision_H_time,content probe disabled,features_v2/-tPCytz4rww__18.npz,eece70451e9c37a4b5f375bf646f66e09113fe3467f35bf1720cc14d986c1776,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-vxjVxOeScU/4,-vxjVxOeScU,4,text,9.127017974853516,6.779999792575836,21,768,92,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7428493962930595,none,True,1.0,-vxjVxOeScU__4.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-vxjVxOeScU__4.npz,00282df9a314394f19d616d561bd1916d1d30dca4c814dfb43630ca164f4c719,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-vxjVxOeScU/4,-vxjVxOeScU,4,audio,9.127017974853516,8.956674983372558,904,74,92,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9813364023221733,F0/HNR naturally undefined or unavailable in 298 rows; support masks retained,True,1.0,-vxjVxOeScU__4.npz::native_audio_*quality*,-vxjVxOeScU__4.npz::query_word_audio_H_time,content probe disabled,features_v2/-vxjVxOeScU__4.npz,00282df9a314394f19d616d561bd1916d1d30dca4c814dfb43630ca164f4c719,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-vxjVxOeScU/4,-vxjVxOeScU,4,vision,9.127017974853516,9.04949951171875,272,35,92,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9915067042326046,none,True,1.0,-vxjVxOeScU__4.npz::native_vision_*quality*,-vxjVxOeScU__4.npz::query_word_vision_H_time,content probe disabled,features_v2/-vxjVxOeScU__4.npz,00282df9a314394f19d616d561bd1916d1d30dca4c814dfb43630ca164f4c719,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wMB_hJL-3o/7,-wMB_hJL-3o,7,text,6.044010162353516,4.440000116825104,22,768,61,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7346116233358853,none,True,1.0,-wMB_hJL-3o__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wMB_hJL-3o__7.npz,04a73d73fd150b07edab8e652fd14c9cb4a0f6e017ea97b17e8146e377b9fae7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wMB_hJL-3o/7,-wMB_hJL-3o,7,audio,6.044010162353516,5.893141134365185,598,74,61,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9750382570618341,F0/HNR naturally undefined or unavailable in 305 rows; support masks retained,True,1.0,-wMB_hJL-3o__7.npz::native_audio_*quality*,-wMB_hJL-3o__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-wMB_hJL-3o__7.npz,04a73d73fd150b07edab8e652fd14c9cb4a0f6e017ea97b17e8146e377b9fae7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wMB_hJL-3o/7,-wMB_hJL-3o,7,vision,6.044010162353516,6.010509967803955,181,35,61,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.994457290168335,OpenFace confidence/success gate rejected 1 native frames,True,0.994475138121547,-wMB_hJL-3o__7.npz::native_vision_*quality*,-wMB_hJL-3o__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-wMB_hJL-3o__7.npz,04a73d73fd150b07edab8e652fd14c9cb4a0f6e017ea97b17e8146e377b9fae7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/1,-wny0OAz3g8,1,text,6.844009876251221,5.439999848604204,23,768,69,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7948556397443339,1 word intervals are not reliable under the fixed-transcript CTC path,True,0.9565217391304348,-wny0OAz3g8__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wny0OAz3g8__1.npz,c5535859f129ce04da5f5b5104d02d4168057b9d947a5349d423278ad4f3d8e8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/1,-wny0OAz3g8,1,audio,6.844009876251221,6.718154723579819,678,74,69,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9816109042875406,F0/HNR naturally undefined or unavailable in 216 rows; support masks retained,True,1.0,-wny0OAz3g8__1.npz::native_audio_*quality*,-wny0OAz3g8__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-wny0OAz3g8__1.npz,c5535859f129ce04da5f5b5104d02d4168057b9d947a5349d423278ad4f3d8e8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/1,-wny0OAz3g8,1,vision,6.844009876251221,6.844009876251221,205,35,69,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-wny0OAz3g8__1.npz::native_vision_*quality*,-wny0OAz3g8__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-wny0OAz3g8__1.npz,c5535859f129ce04da5f5b5104d02d4168057b9d947a5349d423278ad4f3d8e8,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/0,-wny0OAz3g8,0,text,3.3333330154418945,2.260000020265579,9,768,34,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6780000707387991,none,True,1.0,-wny0OAz3g8__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wny0OAz3g8__0.npz,1cc21b4432e2f4b592284ee40558a18acc7d208019117aebdb2f6a1b67ea198d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/0,-wny0OAz3g8,0,audio,3.3333330154418945,3.2479932002119103,328,74,34,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9743980529894127,F0/HNR naturally undefined or unavailable in 96 rows; support masks retained,True,1.0,-wny0OAz3g8__0.npz::native_audio_*quality*,-wny0OAz3g8__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-wny0OAz3g8__0.npz,1cc21b4432e2f4b592284ee40558a18acc7d208019117aebdb2f6a1b67ea198d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/0,-wny0OAz3g8,0,vision,3.3333330154418945,3.3333330154418945,100,35,34,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-wny0OAz3g8__0.npz::native_vision_*quality*,-wny0OAz3g8__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-wny0OAz3g8__0.npz,1cc21b4432e2f4b592284ee40558a18acc7d208019117aebdb2f6a1b67ea198d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/3,-wny0OAz3g8,3,text,6.146028995513916,4.799999862909317,20,768,62,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7809920627470052,none,True,1.0,-wny0OAz3g8__3.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wny0OAz3g8__3.npz,ea917c506bc9fa39460f2b722f7c416f646312bb17cd74baa618905b54837bce,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/3,-wny0OAz3g8,3,audio,6.146028995513916,6.066670619555421,610,74,62,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9870878617695393,F0/HNR naturally undefined or unavailable in 123 rows; support masks retained,True,1.0,-wny0OAz3g8__3.npz::native_audio_*quality*,-wny0OAz3g8__3.npz::query_word_audio_H_time,content probe disabled,features_v2/-wny0OAz3g8__3.npz,ea917c506bc9fa39460f2b722f7c416f646312bb17cd74baa618905b54837bce,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/3,-wny0OAz3g8,3,vision,6.146028995513916,6.146028995513916,184,35,62,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-wny0OAz3g8__3.npz::native_vision_*quality*,-wny0OAz3g8__3.npz::query_word_vision_H_time,content probe disabled,features_v2/-wny0OAz3g8__3.npz,ea917c506bc9fa39460f2b722f7c416f646312bb17cd74baa618905b54837bce,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/2,-wny0OAz3g8,2,text,6.686978816986084,5.400000013411044,23,768,67,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.8075395722346405,none,True,1.0,-wny0OAz3g8__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wny0OAz3g8__2.npz,1825fb37db2e914fb616cd80aebd613afd873eecb7edd4d0b53158bfda0e522a,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/2,-wny0OAz3g8,2,audio,6.686978816986084,6.463760925305857,653,74,67,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9666190221639084,F0/HNR naturally undefined or unavailable in 218 rows; support masks retained,True,1.0,-wny0OAz3g8__2.npz::native_audio_*quality*,-wny0OAz3g8__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-wny0OAz3g8__2.npz,1825fb37db2e914fb616cd80aebd613afd873eecb7edd4d0b53158bfda0e522a,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/2,-wny0OAz3g8,2,vision,6.686978816986084,6.549499988555909,197,35,67,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9794408159210921,none,True,1.0,-wny0OAz3g8__2.npz::native_vision_*quality*,-wny0OAz3g8__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-wny0OAz3g8__2.npz,1825fb37db2e914fb616cd80aebd613afd873eecb7edd4d0b53158bfda0e522a,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/5,-wny0OAz3g8,5,text,6.9119791984558105,5.240000337362289,20,768,70,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7581041821614489,none,True,1.0,-wny0OAz3g8__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wny0OAz3g8__5.npz,6c6a8a4cba654b25190ab1793bfae81c3d2159ecd6cbc02d3fcbd598543acce0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/5,-wny0OAz3g8,5,audio,6.9119791984558105,6.675737296407287,675,74,70,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9658213812186671,F0/HNR naturally undefined or unavailable in 244 rows; support masks retained,True,1.0,-wny0OAz3g8__5.npz::native_audio_*quality*,-wny0OAz3g8__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-wny0OAz3g8__5.npz,6c6a8a4cba654b25190ab1793bfae81c3d2159ecd6cbc02d3fcbd598543acce0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/5,-wny0OAz3g8,5,vision,6.9119791984558105,6.783500194549562,204,35,70,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9814121252079359,none,True,1.0,-wny0OAz3g8__5.npz::native_vision_*quality*,-wny0OAz3g8__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-wny0OAz3g8__5.npz,6c6a8a4cba654b25190ab1793bfae81c3d2159ecd6cbc02d3fcbd598543acce0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/7,-wny0OAz3g8,7,text,8.06796932220459,6.220000401139261,29,768,81,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7709499321000929,none,True,1.0,-wny0OAz3g8__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wny0OAz3g8__7.npz,cbf9cac1048ce191aef781d0c4563eb97f9f3ddd52eb1f9551fa78d31d70e7cd,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/7,-wny0OAz3g8,7,audio,8.06796932220459,7.842202798740284,792,74,81,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9720169333263387,F0/HNR naturally undefined or unavailable in 253 rows; support masks retained,True,1.0,-wny0OAz3g8__7.npz::native_audio_*quality*,-wny0OAz3g8__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-wny0OAz3g8__7.npz,cbf9cac1048ce191aef781d0c4563eb97f9f3ddd52eb1f9551fa78d31d70e7cd,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/7,-wny0OAz3g8,7,vision,8.06796932220459,7.983500003814698,240,35,81,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9895302876081326,none,True,1.0,-wny0OAz3g8__7.npz::native_vision_*quality*,-wny0OAz3g8__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-wny0OAz3g8__7.npz,cbf9cac1048ce191aef781d0c4563eb97f9f3ddd52eb1f9551fa78d31d70e7cd,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/9,-wny0OAz3g8,9,text,6.2130208015441895,4.880000367760659,20,768,63,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.785447292651552,none,True,1.0,-wny0OAz3g8__9.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-wny0OAz3g8__9.npz,b76f70fe783ee76bf1b637137226c98e67d13a6b15ec725894175b36cd76a136,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/9,-wny0OAz3g8,9,audio,6.2130208015441895,6.002310789114722,605,74,63,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9660857384579981,F0/HNR naturally undefined or unavailable in 153 rows; support masks retained,True,1.0,-wny0OAz3g8__9.npz::native_audio_*quality*,-wny0OAz3g8__9.npz::query_word_audio_H_time,content probe disabled,features_v2/-wny0OAz3g8__9.npz,b76f70fe783ee76bf1b637137226c98e67d13a6b15ec725894175b36cd76a136,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/9,-wny0OAz3g8,9,vision,6.2130208015441895,6.083499908447264,183,35,63,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9791533141069262,none,True,1.0,-wny0OAz3g8__9.npz::native_vision_*quality*,-wny0OAz3g8__9.npz::query_word_vision_H_time,content probe disabled,features_v2/-wny0OAz3g8__9.npz,b76f70fe783ee76bf1b637137226c98e67d13a6b15ec725894175b36cd76a136,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/0,-571d8cVauQ,0,text,5.066667079925537,4.120000243186951,12,768,51,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.8131578764096536,none,True,1.0,-571d8cVauQ__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-571d8cVauQ__0.npz,defc62e1530d9b0cd27f2acfe042b71dd84d7e8c85f1df261f04912091976088,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/0,-571d8cVauQ,0,audio,5.066667079925537,4.9044821971171615,500,74,51,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9679898283723906,F0/HNR naturally undefined or unavailable in 325 rows; support masks retained,True,1.0,-571d8cVauQ__0.npz::native_audio_*quality*,-571d8cVauQ__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-571d8cVauQ__0.npz,defc62e1530d9b0cd27f2acfe042b71dd84d7e8c85f1df261f04912091976088,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/0,-571d8cVauQ,0,vision,5.066667079925537,4.6831670701503745,152,35,51,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9243092147706696,OpenFace confidence/success gate rejected 12 native frames,True,0.9210526315789473,-571d8cVauQ__0.npz::native_vision_*quality*,-571d8cVauQ__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-571d8cVauQ__0.npz,defc62e1530d9b0cd27f2acfe042b71dd84d7e8c85f1df261f04912091976088,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/5,-571d8cVauQ,5,text,15.272981643676758,10.139999464154245,43,768,153,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6639174786379944,none,True,1.0,-571d8cVauQ__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-571d8cVauQ__5.npz,e304c03fac1f477823fe0926c4fb421b9c4e4091f85718b729afe856c24a7d3e,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/5,-571d8cVauQ,5,audio,15.272981643676758,14.894732715310278,1512,74,153,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9752341136006616,F0/HNR naturally undefined or unavailable in 816 rows; support masks retained,True,1.0,-571d8cVauQ__5.npz::native_audio_*quality*,-571d8cVauQ__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-571d8cVauQ__5.npz,e304c03fac1f477823fe0926c4fb421b9c4e4091f85718b729afe856c24a7d3e,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/5,-571d8cVauQ,5,vision,15.272981643676758,15.149499893188478,455,35,153,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9919150200419835,none,True,1.0,-571d8cVauQ__5.npz::native_vision_*quality*,-571d8cVauQ__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-571d8cVauQ__5.npz,e304c03fac1f477823fe0926c4fb421b9c4e4091f85718b729afe856c24a7d3e,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/1,-I_e4mIh0yE,1,text,7.622004985809326,4.1400005519390115,18,768,77,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5431642408587869,none,True,1.0,-I_e4mIh0yE__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-I_e4mIh0yE__1.npz,8453a966d4bd1f0179eb86a35cd34c0864a3a3bc2fa86f37783060cc0d14f5df,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/1,-I_e4mIh0yE,1,audio,7.622004985809326,7.303968804913598,745,74,77,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9582739474078213,F0/HNR naturally undefined or unavailable in 519 rows; support masks retained,True,1.0,-I_e4mIh0yE__1.npz::native_audio_*quality*,-I_e4mIh0yE__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-I_e4mIh0yE__1.npz,8453a966d4bd1f0179eb86a35cd34c0864a3a3bc2fa86f37783060cc0d14f5df,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/1,-I_e4mIh0yE,1,vision,7.622004985809326,7.4835000038146955,225,35,77,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9818282745481668,none,True,1.0,-I_e4mIh0yE__1.npz::native_vision_*quality*,-I_e4mIh0yE__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-I_e4mIh0yE__1.npz,8453a966d4bd1f0179eb86a35cd34c0864a3a3bc2fa86f37783060cc0d14f5df,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/3,-I_e4mIh0yE,3,text,9.163021087646484,5.839999616146088,22,768,92,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6373443387595747,none,True,1.0,-I_e4mIh0yE__3.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-I_e4mIh0yE__3.npz,a015f02ae51d74c42ad27e2a0831263a303f0658ac8d984819e27ad61342b23e,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/3,-I_e4mIh0yE,3,audio,9.163021087646484,8.82051541032018,900,74,92,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.962620878632696,F0/HNR naturally undefined or unavailable in 643 rows; support masks retained,True,1.0,-I_e4mIh0yE__3.npz::native_audio_*quality*,-I_e4mIh0yE__3.npz::query_word_audio_H_time,content probe disabled,features_v2/-I_e4mIh0yE__3.npz,a015f02ae51d74c42ad27e2a0831263a303f0658ac8d984819e27ad61342b23e,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/3,-I_e4mIh0yE,3,vision,9.163021087646484,9.016500473022463,271,35,92,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9840095735650374,none,True,1.0,-I_e4mIh0yE__3.npz::native_vision_*quality*,-I_e4mIh0yE__3.npz::query_word_vision_H_time,content probe disabled,features_v2/-I_e4mIh0yE__3.npz,a015f02ae51d74c42ad27e2a0831263a303f0658ac8d984819e27ad61342b23e,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/10,-UacrmKiTn4,10,text,7.361979007720947,4.499999731779098,18,768,74,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.61124865026913,none,True,1.0,-UacrmKiTn4__10.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-UacrmKiTn4__10.npz,00312e0602dbeac970de5d7dd0e8a970e514fe45d7fe6625b4f454b299e36360,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/10,-UacrmKiTn4,10,audio,7.361979007720947,7.145256002207061,725,74,74,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9705618550003204,F0/HNR naturally undefined or unavailable in 359 rows; support masks retained,True,1.0,-UacrmKiTn4__10.npz::native_audio_*quality*,-UacrmKiTn4__10.npz::query_word_audio_H_time,content probe disabled,features_v2/-UacrmKiTn4__10.npz,00312e0602dbeac970de5d7dd0e8a970e514fe45d7fe6625b4f454b299e36360,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/10,-UacrmKiTn4,10,vision,7.361979007720947,7.249499797821047,218,35,74,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9847216068149696,none,True,1.0,-UacrmKiTn4__10.npz::native_vision_*quality*,-UacrmKiTn4__10.npz::query_word_vision_H_time,content probe disabled,features_v2/-UacrmKiTn4__10.npz,00312e0602dbeac970de5d7dd0e8a970e514fe45d7fe6625b4f454b299e36360,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/4,-UacrmKiTn4,4,text,4.741015911102295,3.3999994397163382,14,768,48,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7171457559875246,none,True,1.0,-UacrmKiTn4__4.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-UacrmKiTn4__4.npz,a7f2ab0d6ee2e2a2d5cd4239f03e6cac67c618ddb744891323df3ff87e29af84,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/4,-UacrmKiTn4,4,audio,4.741015911102295,4.6206091175208215,469,74,48,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.974603166106338,F0/HNR naturally undefined or unavailable in 227 rows; support masks retained,True,1.0,-UacrmKiTn4__4.npz::native_audio_*quality*,-UacrmKiTn4__4.npz::query_word_audio_H_time,content probe disabled,features_v2/-UacrmKiTn4__4.npz,a7f2ab0d6ee2e2a2d5cd4239f03e6cac67c618ddb744891323df3ff87e29af84,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/4,-UacrmKiTn4,4,vision,4.741015911102295,4.741015911102295,142,35,48,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-UacrmKiTn4__4.npz::native_vision_*quality*,-UacrmKiTn4__4.npz::query_word_vision_H_time,content probe disabled,features_v2/-UacrmKiTn4__4.npz,a7f2ab0d6ee2e2a2d5cd4239f03e6cac67c618ddb744891323df3ff87e29af84,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/7,-hnBHBN8p5A,7,text,7.372000217437744,5.039999097585677,13,768,74,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.683667790142498,none,True,1.0,-hnBHBN8p5A__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-hnBHBN8p5A__7.npz,0ec6fb76226315d3fafbb483e3500e60cc8a6729617b10e4b4b76163595080fb,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/7,-hnBHBN8p5A,7,audio,7.372000217437744,7.198113807149835,731,74,74,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9764125874716338,F0/HNR naturally undefined or unavailable in 409 rows; support masks retained,True,1.0,-hnBHBN8p5A__7.npz::native_audio_*quality*,-hnBHBN8p5A__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-hnBHBN8p5A__7.npz,0ec6fb76226315d3fafbb483e3500e60cc8a6729617b10e4b4b76163595080fb,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/7,-hnBHBN8p5A,7,vision,7.372000217437744,5.964499384164811,176,35,74,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.8090747705156562,OpenFace confidence/success gate rejected 33 native frames,True,0.8579545454545454,-hnBHBN8p5A__7.npz::native_vision_*quality*,-hnBHBN8p5A__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-hnBHBN8p5A__7.npz,0ec6fb76226315d3fafbb483e3500e60cc8a6729617b10e4b4b76163595080fb,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/6,-hnBHBN8p5A,6,text,6.401000022888184,4.1200003027915955,13,768,65,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6436494747788827,none,True,1.0,-hnBHBN8p5A__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-hnBHBN8p5A__6.npz,997f6808ac3973ddd71800e91c8e10755c10b5037186d84348a79e00d672fdc7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/6,-hnBHBN8p5A,6,audio,6.401000022888184,6.214421288387195,634,74,65,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.970851627271702,F0/HNR naturally undefined or unavailable in 438 rows; support masks retained,True,1.0,-hnBHBN8p5A__6.npz::native_audio_*quality*,-hnBHBN8p5A__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-hnBHBN8p5A__6.npz,997f6808ac3973ddd71800e91c8e10755c10b5037186d84348a79e00d672fdc7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/6,-hnBHBN8p5A,6,vision,6.401000022888184,4.733499767258764,153,35,65,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.7394937900848453,OpenFace confidence/success gate rejected 40 native frames,True,0.7581699346405228,-hnBHBN8p5A__6.npz::native_vision_*quality*,-hnBHBN8p5A__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-hnBHBN8p5A__6.npz,997f6808ac3973ddd71800e91c8e10755c10b5037186d84348a79e00d672fdc7,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-qDkUB0GgYY/6,-qDkUB0GgYY,6,text,4.677995204925537,3.4799999855458736,16,768,47,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7439084122792013,none,True,1.0,-qDkUB0GgYY__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-qDkUB0GgYY__6.npz,ab19bf4a761d3919b7113b1104ccaadbbf0c4e7b45694904d094280a2c329c8b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-qDkUB0GgYY/6,-qDkUB0GgYY,6,audio,4.677995204925537,4.568314991891383,462,74,47,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9765540133690881,F0/HNR naturally undefined or unavailable in 172 rows; support masks retained,True,1.0,-qDkUB0GgYY__6.npz::native_audio_*quality*,-qDkUB0GgYY__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-qDkUB0GgYY__6.npz,ab19bf4a761d3919b7113b1104ccaadbbf0c4e7b45694904d094280a2c329c8b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-qDkUB0GgYY/6,-qDkUB0GgYY,6,vision,4.677995204925537,4.677995204925537,140,35,47,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-qDkUB0GgYY__6.npz::native_vision_*quality*,-qDkUB0GgYY__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-qDkUB0GgYY__6.npz,ab19bf4a761d3919b7113b1104ccaadbbf0c4e7b45694904d094280a2c329c8b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-uywlfIYOS8/4,-uywlfIYOS8,4,text,6.044987201690674,3.9800001978874198,18,768,61,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6583967947482644,none,True,1.0,-uywlfIYOS8__4.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-uywlfIYOS8__4.npz,fde46f5222cf5cfec7af9c93214c972ce2d686b0bf9c90a291021601968986f9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-uywlfIYOS8/4,-uywlfIYOS8,4,audio,6.044987201690674,5.83868071376472,589,74,61,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9658714764742176,F0/HNR naturally undefined or unavailable in 158 rows; support masks retained,True,1.0,-uywlfIYOS8__4.npz::native_audio_*quality*,-uywlfIYOS8__4.npz::query_word_audio_H_time,content probe disabled,features_v2/-uywlfIYOS8__4.npz,fde46f5222cf5cfec7af9c93214c972ce2d686b0bf9c90a291021601968986f9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-uywlfIYOS8/4,-uywlfIYOS8,4,vision,6.044987201690674,5.949500083923341,179,35,61,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9842039172985135,none,True,1.0,-uywlfIYOS8__4.npz::native_vision_*quality*,-uywlfIYOS8__4.npz::query_word_vision_H_time,content probe disabled,features_v2/-uywlfIYOS8__4.npz,fde46f5222cf5cfec7af9c93214c972ce2d686b0bf9c90a291021601968986f9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-6rXp3zJ3kc/8,-6rXp3zJ3kc,8,text,12.805012702941895,7.859999686479568,34,768,129,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6138220920838109,none,True,1.0,-6rXp3zJ3kc__8.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-6rXp3zJ3kc__8.npz,59b77a0f873b7fa95e7327b93d64bb5a4bdae84039321988ef70bba8e63ee1ae,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-6rXp3zJ3kc/8,-6rXp3zJ3kc,8,audio,12.805012702941895,12.588114851229895,1274,74,129,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9830614887510288,F0/HNR naturally undefined or unavailable in 555 rows; support masks retained,True,1.0,-6rXp3zJ3kc__8.npz::native_audio_*quality*,-6rXp3zJ3kc__8.npz::query_word_audio_H_time,content probe disabled,features_v2/-6rXp3zJ3kc__8.npz,59b77a0f873b7fa95e7327b93d64bb5a4bdae84039321988ef70bba8e63ee1ae,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-6rXp3zJ3kc/8,-6rXp3zJ3kc,8,vision,12.805012702941895,12.805012702941895,384,35,129,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-6rXp3zJ3kc__8.npz::native_vision_*quality*,-6rXp3zJ3kc__8.npz::query_word_vision_H_time,content probe disabled,features_v2/-6rXp3zJ3kc__8.npz,59b77a0f873b7fa95e7327b93d64bb5a4bdae84039321988ef70bba8e63ee1ae,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/0,-9y-fZ3swSY,0,text,6.8333330154418945,4.499999579042196,22,768,69,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.658536554397853,none,True,1.0,-9y-fZ3swSY__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-9y-fZ3swSY__0.npz,29a1fd181293cdbe24e50edcad51e3cc0f27a5824d840bcc39d453b272fb6e3c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/0,-9y-fZ3swSY,0,audio,6.8333330154418945,6.691547258027099,676,74,69,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9792508638033023,F0/HNR naturally undefined or unavailable in 242 rows; support masks retained,True,1.0,-9y-fZ3swSY__0.npz::native_audio_*quality*,-9y-fZ3swSY__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-9y-fZ3swSY__0.npz,29a1fd181293cdbe24e50edcad51e3cc0f27a5824d840bcc39d453b272fb6e3c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/0,-9y-fZ3swSY,0,vision,6.8333330154418945,6.8333330154418945,205,35,69,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-9y-fZ3swSY__0.npz::native_vision_*quality*,-9y-fZ3swSY__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-9y-fZ3swSY__0.npz,29a1fd181293cdbe24e50edcad51e3cc0f27a5824d840bcc39d453b272fb6e3c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/4,-9y-fZ3swSY,4,text,2.8210289478302,1.6199999749660492,9,768,29,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.574258543575767,none,True,1.0,-9y-fZ3swSY__4.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-9y-fZ3swSY__4.npz,fe04a26710e85c58bdaad5a48618c7e0f742d82b0d920eb4a046c8f14a6921dc,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/4,-9y-fZ3swSY,4,audio,2.8210289478302,2.624178272646827,267,74,29,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9302202569261895,F0/HNR naturally undefined or unavailable in 150 rows; support masks retained,True,1.0,-9y-fZ3swSY__4.npz::native_audio_*quality*,-9y-fZ3swSY__4.npz::query_word_audio_H_time,content probe disabled,features_v2/-9y-fZ3swSY__4.npz,fe04a26710e85c58bdaad5a48618c7e0f742d82b0d920eb4a046c8f14a6921dc,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/4,-9y-fZ3swSY,4,vision,2.8210289478302,2.683500051498412,81,35,29,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9512486759706708,none,True,1.0,-9y-fZ3swSY__4.npz::native_vision_*quality*,-9y-fZ3swSY__4.npz::query_word_vision_H_time,content probe disabled,features_v2/-9y-fZ3swSY__4.npz,fe04a26710e85c58bdaad5a48618c7e0f742d82b0d920eb4a046c8f14a6921dc,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/8,-9y-fZ3swSY,8,text,4.988996982574463,2.9200008809566502,12,768,50,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5852881633634197,none,True,1.0,-9y-fZ3swSY__8.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-9y-fZ3swSY__8.npz,6140d031e614b62a5d8df73346fef2417c28cee3501310aa95aa40d358b4b256,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/8,-9y-fZ3swSY,8,audio,4.988996982574463,4.840736327944576,492,74,50,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9702824725796125,F0/HNR naturally undefined or unavailable in 282 rows; support masks retained,True,1.0,-9y-fZ3swSY__8.npz::native_audio_*quality*,-9y-fZ3swSY__8.npz::query_word_audio_H_time,content probe disabled,features_v2/-9y-fZ3swSY__8.npz,6140d031e614b62a5d8df73346fef2417c28cee3501310aa95aa40d358b4b256,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/8,-9y-fZ3swSY,8,vision,4.988996982574463,4.949500083923339,149,35,50,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.992083198528867,none,True,1.0,-9y-fZ3swSY__8.npz::native_vision_*quality*,-9y-fZ3swSY__8.npz::query_word_vision_H_time,content probe disabled,features_v2/-9y-fZ3swSY__8.npz,6140d031e614b62a5d8df73346fef2417c28cee3501310aa95aa40d358b4b256,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-AUZQgSxyPQ/2,-AUZQgSxyPQ,2,text,22.511003494262695,15.860002636909487,41,768,226,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7045444527140549,none,True,1.0,-AUZQgSxyPQ__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-AUZQgSxyPQ__2.npz,e09034b30bb4f8fb9f9c2568afc20f2a764e327fe6ef4e13f21804eaac51bb7b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-AUZQgSxyPQ/2,-AUZQgSxyPQ,2,audio,22.511003494262695,21.84015408077756,2241,74,226,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9701990444959012,F0/HNR naturally undefined or unavailable in 2088 rows; support masks retained,True,1.0,-AUZQgSxyPQ__2.npz::native_audio_*quality*,-AUZQgSxyPQ__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-AUZQgSxyPQ__2.npz,e09034b30bb4f8fb9f9c2568afc20f2a764e327fe6ef4e13f21804eaac51bb7b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-AUZQgSxyPQ/2,-AUZQgSxyPQ,2,vision,22.511003494262695,22.449501037597653,674,35,226,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9972678936023125,none,True,1.0,-AUZQgSxyPQ__2.npz::native_vision_*quality*,-AUZQgSxyPQ__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-AUZQgSxyPQ__2.npz,e09034b30bb4f8fb9f9c2568afc20f2a764e327fe6ef4e13f21804eaac51bb7b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HeZS2-Prhc/2,-HeZS2-Prhc,2,text,8.261979103088379,3.9600005447864532,16,768,83,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.479304110477135,none,True,1.0,-HeZS2-Prhc__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-HeZS2-Prhc__2.npz,edb0fa22fdfbd990c8174764af78670bd2ecad799240d35d96a1124a3cd9dc0c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HeZS2-Prhc/2,-HeZS2-Prhc,2,audio,8.261979103088379,8.037157203700092,816,74,83,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9727883723036472,F0/HNR naturally undefined or unavailable in 421 rows; support masks retained,True,1.0,-HeZS2-Prhc__2.npz::native_audio_*quality*,-HeZS2-Prhc__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-HeZS2-Prhc__2.npz,edb0fa22fdfbd990c8174764af78670bd2ecad799240d35d96a1124a3cd9dc0c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HeZS2-Prhc/2,-HeZS2-Prhc,2,vision,8.261979103088379,8.183500289916994,246,35,83,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.990501208948586,none,True,1.0,-HeZS2-Prhc__2.npz::native_vision_*quality*,-HeZS2-Prhc__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-HeZS2-Prhc__2.npz,edb0fa22fdfbd990c8174764af78670bd2ecad799240d35d96a1124a3cd9dc0c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/0,-MeTTeMJBNc,0,text,9.300000190734863,5.50000023841858,21,768,94,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5913978629697193,none,True,1.0,-MeTTeMJBNc__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-MeTTeMJBNc__0.npz,54e2097f7bdb455d243e72254e5af7ed16160739f2f3cfd22f57d2729ec1acbe,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/0,-MeTTeMJBNc,0,audio,9.300000190734863,9.066049653130609,922,74,94,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9748440287305232,F0/HNR naturally undefined or unavailable in 563 rows; support masks retained,True,1.0,-MeTTeMJBNc__0.npz::native_audio_*quality*,-MeTTeMJBNc__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-MeTTeMJBNc__0.npz,54e2097f7bdb455d243e72254e5af7ed16160739f2f3cfd22f57d2729ec1acbe,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/0,-MeTTeMJBNc,0,vision,9.300000190734863,8.916500180959703,279,35,94,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9587634406548483,OpenFace confidence/success gate rejected 12 native frames,True,0.956989247311828,-MeTTeMJBNc__0.npz::native_vision_*quality*,-MeTTeMJBNc__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-MeTTeMJBNc__0.npz,54e2097f7bdb455d243e72254e5af7ed16160739f2f3cfd22f57d2729ec1acbe,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/13,-MeTTeMJBNc,13,text,5.430013179779053,3.7600001394748688,14,768,55,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6924477003991109,none,True,1.0,-MeTTeMJBNc__13.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-MeTTeMJBNc__13.npz,80c5d996e5d7b61c58e1fe32d706efcaf80c8c8cddb5c37a5c66d527786c2a44,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/13,-MeTTeMJBNc,13,audio,5.430013179779053,5.165986305153049,526,74,55,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9513763842030403,F0/HNR naturally undefined or unavailable in 332 rows; support masks retained,True,1.0,-MeTTeMJBNc__13.npz::native_audio_*quality*,-MeTTeMJBNc__13.npz::query_word_audio_H_time,content probe disabled,features_v2/-MeTTeMJBNc__13.npz,80c5d996e5d7b61c58e1fe32d706efcaf80c8c8cddb5c37a5c66d527786c2a44,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/13,-MeTTeMJBNc,13,vision,5.430013179779053,5.2835001945495605,159,35,55,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9730179319315292,none,True,1.0,-MeTTeMJBNc__13.npz::native_vision_*quality*,-MeTTeMJBNc__13.npz::query_word_vision_H_time,content probe disabled,features_v2/-MeTTeMJBNc__13.npz,80c5d996e5d7b61c58e1fe32d706efcaf80c8c8cddb5c37a5c66d527786c2a44,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/7,-MeTTeMJBNc,7,text,10.51699161529541,7.359996646642682,31,768,106,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6998195791977865,none,True,1.0,-MeTTeMJBNc__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-MeTTeMJBNc__7.npz,40716dc7402fd6398038eded58ce939ec76be6afbeea346b968bf4c427088f3a,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/7,-MeTTeMJBNc,7,audio,10.51699161529541,10.274333249878238,1042,74,106,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9769270173169805,F0/HNR naturally undefined or unavailable in 507 rows; support masks retained,True,1.0,-MeTTeMJBNc__7.npz::native_audio_*quality*,-MeTTeMJBNc__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-MeTTeMJBNc__7.npz,40716dc7402fd6398038eded58ce939ec76be6afbeea346b968bf4c427088f3a,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/7,-MeTTeMJBNc,7,vision,10.51699161529541,10.449499130249023,314,35,106,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.993582529347249,none,True,1.0,-MeTTeMJBNc__7.npz::native_vision_*quality*,-MeTTeMJBNc__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-MeTTeMJBNc__7.npz,40716dc7402fd6398038eded58ce939ec76be6afbeea346b968bf4c427088f3a,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/11,-RfYyzHpjk4,11,text,5.238996982574463,3.3799998164176945,17,768,53,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6451616268648336,none,True,1.0,-RfYyzHpjk4__11.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-RfYyzHpjk4__11.npz,96e481cd732001239639c8cb4e7f94e570925e5a15940920ffab0cec1f13f904,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/11,-RfYyzHpjk4,11,audio,5.238996982574463,5.023697516402681,508,74,53,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9589044492890731,F0/HNR naturally undefined or unavailable in 189 rows; support masks retained,True,1.0,-RfYyzHpjk4__11.npz::native_audio_*quality*,-RfYyzHpjk4__11.npz::query_word_audio_H_time,content probe disabled,features_v2/-RfYyzHpjk4__11.npz,96e481cd732001239639c8cb4e7f94e570925e5a15940920ffab0cec1f13f904,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/11,-RfYyzHpjk4,11,vision,5.238996982574463,5.116499900817871,154,35,53,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9766182186849827,none,True,1.0,-RfYyzHpjk4__11.npz::native_vision_*quality*,-RfYyzHpjk4__11.npz::query_word_vision_H_time,content probe disabled,features_v2/-RfYyzHpjk4__11.npz,96e481cd732001239639c8cb4e7f94e570925e5a15940920ffab0cec1f13f904,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/8,-RfYyzHpjk4,8,text,4.588996887207031,3.1199999749660483,17,768,46,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6798871412756508,none,True,1.0,-RfYyzHpjk4__8.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-RfYyzHpjk4__8.npz,ed8c672c532b8c7d1c830c82d6e2c14fcb3bbd3dc1a86670a03a6c8e83c02f3c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/8,-RfYyzHpjk4,8,audio,4.588996887207031,4.483190728200448,454,74,46,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9769435103995071,F0/HNR naturally undefined or unavailable in 182 rows; support masks retained,True,1.0,-RfYyzHpjk4__8.npz::native_audio_*quality*,-RfYyzHpjk4__8.npz::query_word_audio_H_time,content probe disabled,features_v2/-RfYyzHpjk4__8.npz,ed8c672c532b8c7d1c830c82d6e2c14fcb3bbd3dc1a86670a03a6c8e83c02f3c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/8,-RfYyzHpjk4,8,vision,4.588996887207031,4.549499988555907,137,35,46,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9913931302151824,none,True,1.0,-RfYyzHpjk4__8.npz::native_vision_*quality*,-RfYyzHpjk4__8.npz::query_word_vision_H_time,content probe disabled,features_v2/-RfYyzHpjk4__8.npz,ed8c672c532b8c7d1c830c82d6e2c14fcb3bbd3dc1a86670a03a6c8e83c02f3c,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/2,-RfYyzHpjk4,2,text,5.516016006469727,3.6999993324279785,25,768,56,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6707738570896559,none,True,1.0,-RfYyzHpjk4__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-RfYyzHpjk4__2.npz,5e428ee22de58c9d6561b9b4353bef9dfb811a5841d9ab07b5bc453d697ad17f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/2,-RfYyzHpjk4,2,audio,5.516016006469727,5.341840220464243,540,74,56,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9684236257108041,F0/HNR naturally undefined or unavailable in 185 rows; support masks retained,True,1.0,-RfYyzHpjk4__2.npz::native_audio_*quality*,-RfYyzHpjk4__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-RfYyzHpjk4__2.npz,5e428ee22de58c9d6561b9b4353bef9dfb811a5841d9ab07b5bc453d697ad17f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/2,-RfYyzHpjk4,2,vision,5.516016006469727,5.44950008392334,164,35,56,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9879413108177405,none,True,1.0,-RfYyzHpjk4__2.npz::native_vision_*quality*,-RfYyzHpjk4__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-RfYyzHpjk4__2.npz,5e428ee22de58c9d6561b9b4353bef9dfb811a5841d9ab07b5bc453d697ad17f,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UUCSKoHeMA/0,-UUCSKoHeMA,0,text,7.800000190734863,4.980000615119934,18,768,79,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6384616017106471,none,True,1.0,-UUCSKoHeMA__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-UUCSKoHeMA__0.npz,3f9792888ec8e969f61cfb6cb4de7bdf7ef8944afe0a9d2a9d13586e1f13b898,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UUCSKoHeMA/0,-UUCSKoHeMA,0,audio,7.800000190734863,7.652800917826794,774,74,79,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9811282988065926,F0/HNR naturally undefined or unavailable in 294 rows; support masks retained,True,1.0,-UUCSKoHeMA__0.npz::native_audio_*quality*,-UUCSKoHeMA__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-UUCSKoHeMA__0.npz,3f9792888ec8e969f61cfb6cb4de7bdf7ef8944afe0a9d2a9d13586e1f13b898,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UUCSKoHeMA/0,-UUCSKoHeMA,0,vision,7.800000190734863,7.416500180959701,234,35,79,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9508333332823891,OpenFace confidence/success gate rejected 12 native frames,True,0.9487179487179487,-UUCSKoHeMA__0.npz::native_vision_*quality*,-UUCSKoHeMA__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-UUCSKoHeMA__0.npz,3f9792888ec8e969f61cfb6cb4de7bdf7ef8944afe0a9d2a9d13586e1f13b898,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/0,-ri04Z7vwnc,0,text,2.806999921798706,2.1600002199411397,8,768,29,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7695049091975131,none,True,1.0,-ri04Z7vwnc__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-ri04Z7vwnc__0.npz,93021c70c8fad20ad3fded80e7fc790a2f9c96835de58a3b694c6906de42684b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/0,-ri04Z7vwnc,0,audio,2.806999921798706,2.7302467235036803,277,74,29,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9726565014487628,F0/HNR naturally undefined or unavailable in 122 rows; support masks retained,True,1.0,-ri04Z7vwnc__0.npz::native_audio_*quality*,-ri04Z7vwnc__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-ri04Z7vwnc__0.npz,93021c70c8fad20ad3fded80e7fc790a2f9c96835de58a3b694c6906de42684b,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/0,-ri04Z7vwnc,0,vision,2.806999921798706,0.0,67,35,29,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.0,OpenFace confidence/success gate rejected 67 native frames,False,0.0,-ri04Z7vwnc__0.npz::native_vision_*quality*,-ri04Z7vwnc__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-ri04Z7vwnc__0.npz,93021c70c8fad20ad3fded80e7fc790a2f9c96835de58a3b694c6906de42684b,failed_or_unavailable,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/2,-ri04Z7vwnc,2,text,6.0269999504089355,4.539999946951865,16,768,61,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7532769179206354,none,True,1.0,-ri04Z7vwnc__2.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-ri04Z7vwnc__2.npz,f5993ce3a6ee56298462c08692a96f78c7d914d7264257ed401de7d6e17fd148,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/2,-ri04Z7vwnc,2,audio,6.0269999504089355,5.824690816370216,592,74,61,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9664328628333584,F0/HNR naturally undefined or unavailable in 328 rows; support masks retained,True,1.0,-ri04Z7vwnc__2.npz::native_audio_*quality*,-ri04Z7vwnc__2.npz::query_word_audio_H_time,content probe disabled,features_v2/-ri04Z7vwnc__2.npz,f5993ce3a6ee56298462c08692a96f78c7d914d7264257ed401de7d6e17fd148,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/2,-ri04Z7vwnc,2,vision,6.0269999504089355,1.9609997272491455,143,35,61,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.32536912948142477,OpenFace confidence/success gate rejected 96 native frames,True,0.32867132867132864,-ri04Z7vwnc__2.npz::native_vision_*quality*,-ri04Z7vwnc__2.npz::query_word_vision_H_time,content probe disabled,features_v2/-ri04Z7vwnc__2.npz,f5993ce3a6ee56298462c08692a96f78c7d914d7264257ed401de7d6e17fd148,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/5,-ri04Z7vwnc,5,text,3.5450000762939453,1.9600001275539394,9,768,36,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.5528914204151401,1 word intervals are not reliable under the fixed-transcript CTC path,True,0.8888888888888888,-ri04Z7vwnc__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-ri04Z7vwnc__5.npz,2a5e4319bf592a18c8eb5117f7c7ec30c6c79528f9eac9fe4fb85bf70f277736,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/5,-ri04Z7vwnc,5,audio,3.5450000762939453,3.3887803554534908,344,74,36,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9559323787085016,F0/HNR naturally undefined or unavailable in 188 rows; support masks retained,True,1.0,-ri04Z7vwnc__5.npz::native_audio_*quality*,-ri04Z7vwnc__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-ri04Z7vwnc__5.npz,2a5e4319bf592a18c8eb5117f7c7ec30c6c79528f9eac9fe4fb85bf70f277736,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/5,-ri04Z7vwnc,5,vision,3.5450000762939453,3.4409999866038565,83,35,36,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9706628808316378,none,True,1.0,-ri04Z7vwnc__5.npz::native_vision_*quality*,-ri04Z7vwnc__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-ri04Z7vwnc__5.npz,2a5e4319bf592a18c8eb5117f7c7ec30c6c79528f9eac9fe4fb85bf70f277736,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/1,-s9qJ7ATP7w,1,text,4.7919921875,2.760000109672546,11,768,48,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.575960895109983,none,True,1.0,-s9qJ7ATP7w__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-s9qJ7ATP7w__1.npz,a33e8f82a185bcb7648b523927308cdc2142e3076479ba2b45cd0e828636fc09,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/1,-s9qJ7ATP7w,1,audio,4.7919921875,4.563759450976914,465,74,48,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9523720557979132,F0/HNR naturally undefined or unavailable in 296 rows; support masks retained,True,1.0,-s9qJ7ATP7w__1.npz::native_audio_*quality*,-s9qJ7ATP7w__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__1.npz,a33e8f82a185bcb7648b523927308cdc2142e3076479ba2b45cd0e828636fc09,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/1,-s9qJ7ATP7w,1,vision,4.7919921875,4.6494998931884775,140,35,48,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9702644977837784,none,True,1.0,-s9qJ7ATP7w__1.npz::native_vision_*quality*,-s9qJ7ATP7w__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__1.npz,a33e8f82a185bcb7648b523927308cdc2142e3076479ba2b45cd0e828636fc09,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/0,-s9qJ7ATP7w,0,text,6.800000190734863,4.260000094771386,24,768,69,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.626470584600236,none,True,1.0,-s9qJ7ATP7w__0.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-s9qJ7ATP7w__0.npz,f2324dbc6debe0d1e4254e6f943523373e3cbb30d8d0555413ce3a7b370a4af5,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/0,-s9qJ7ATP7w,0,audio,6.800000190734863,6.63685745967401,672,74,69,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9760084225757614,F0/HNR naturally undefined or unavailable in 273 rows; support masks retained,True,1.0,-s9qJ7ATP7w__0.npz::native_audio_*quality*,-s9qJ7ATP7w__0.npz::query_word_audio_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__0.npz,f2324dbc6debe0d1e4254e6f943523373e3cbb30d8d0555413ce3a7b370a4af5,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/0,-s9qJ7ATP7w,0,vision,6.800000190734863,6.800000190734863,204,35,69,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,1.0,none,True,1.0,-s9qJ7ATP7w__0.npz::native_vision_*quality*,-s9qJ7ATP7w__0.npz::query_word_vision_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__0.npz,f2324dbc6debe0d1e4254e6f943523373e3cbb30d8d0555413ce3a7b370a4af5,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/5,-s9qJ7ATP7w,5,text,8.561002731323242,5.1399999763816595,19,768,86,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6003969555546665,none,True,1.0,-s9qJ7ATP7w__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-s9qJ7ATP7w__5.npz,bd35eb8de64ce1f8b27921294598f7cf5bb971255738f51d06458c7096c58eae,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/5,-s9qJ7ATP7w,5,audio,8.561002731323242,8.295629897633114,840,74,86,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9690021318742051,F0/HNR naturally undefined or unavailable in 378 rows; support masks retained,True,1.0,-s9qJ7ATP7w__5.npz::native_audio_*quality*,-s9qJ7ATP7w__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__5.npz,bd35eb8de64ce1f8b27921294598f7cf5bb971255738f51d06458c7096c58eae,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/5,-s9qJ7ATP7w,5,vision,8.561002731323242,8.416500091552736,253,35,86,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9831208277458204,none,True,1.0,-s9qJ7ATP7w__5.npz::native_vision_*quality*,-s9qJ7ATP7w__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__5.npz,bd35eb8de64ce1f8b27921294598f7cf5bb971255738f51d06458c7096c58eae,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/4,-s9qJ7ATP7w,4,text,4.427018165588379,2.859999973326921,9,768,45,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6460330331503866,none,True,1.0,-s9qJ7ATP7w__4.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-s9qJ7ATP7w__4.npz,76e0672ffce5857a789b41fb10606402f7da58c33579b1032a8282f86fb35bc9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/4,-s9qJ7ATP7w,4,audio,4.427018165588379,4.188183940745689,425,74,45,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9460507691838334,F0/HNR naturally undefined or unavailable in 217 rows; support masks retained,True,1.0,-s9qJ7ATP7w__4.npz::native_audio_*quality*,-s9qJ7ATP7w__4.npz::query_word_audio_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__4.npz,76e0672ffce5857a789b41fb10606402f7da58c33579b1032a8282f86fb35bc9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/4,-s9qJ7ATP7w,4,vision,4.427018165588379,4.150500178337097,129,35,45,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9375385469613201,OpenFace confidence/success gate rejected 4 native frames,True,0.9689922480620154,-s9qJ7ATP7w__4.npz::native_vision_*quality*,-s9qJ7ATP7w__4.npz::query_word_vision_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__4.npz,76e0672ffce5857a789b41fb10606402f7da58c33579b1032a8282f86fb35bc9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/7,-s9qJ7ATP7w,7,text,6.966015815734863,4.559999305754899,28,768,70,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6546065105759242,none,True,1.0,-s9qJ7ATP7w__7.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-s9qJ7ATP7w__7.npz,5301f4403cf7cd299cc525d2f443392ac70fd70c3c9cd4a09406f2ba4df6734d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/7,-s9qJ7ATP7w,7,audio,6.966015815734863,6.713249155597107,684,74,70,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9637143143478363,F0/HNR naturally undefined or unavailable in 464 rows; support masks retained,True,1.0,-s9qJ7ATP7w__7.npz::native_audio_*quality*,-s9qJ7ATP7w__7.npz::query_word_audio_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__7.npz,5301f4403cf7cd299cc525d2f443392ac70fd70c3c9cd4a09406f2ba4df6734d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/7,-s9qJ7ATP7w,7,vision,6.966015815734863,5.083500146865846,207,35,70,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.7297571928250889,OpenFace confidence/success gate rejected 54 native frames,True,0.7391304347826086,-s9qJ7ATP7w__7.npz::native_vision_*quality*,-s9qJ7ATP7w__7.npz::query_word_vision_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__7.npz,5301f4403cf7cd299cc525d2f443392ac70fd70c3c9cd4a09406f2ba4df6734d,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/6,-s9qJ7ATP7w,6,text,2.4749999046325684,1.660000160336494,5,768,25,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6707071613333792,none,True,1.0,-s9qJ7ATP7w__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-s9qJ7ATP7w__6.npz,40b684fd1b4559f0f82e72f9b47f4ce7c5e15059fe8d0d8ab112637cd3386451,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/6,-s9qJ7ATP7w,6,audio,2.4749999046325684,2.258232190802291,232,74,25,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9124170819463291,F0/HNR naturally undefined or unavailable in 209 rows; support masks retained,True,1.0,-s9qJ7ATP7w__6.npz::native_audio_*quality*,-s9qJ7ATP7w__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__6.npz,40b684fd1b4559f0f82e72f9b47f4ce7c5e15059fe8d0d8ab112637cd3386451,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/6,-s9qJ7ATP7w,6,vision,2.4749999046325684,1.9500000476837156,71,35,25,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.7878788375037159,OpenFace confidence/success gate rejected 12 native frames,True,0.8309859154929577,-s9qJ7ATP7w__6.npz::native_vision_*quality*,-s9qJ7ATP7w__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__6.npz,40b684fd1b4559f0f82e72f9b47f4ce7c5e15059fe8d0d8ab112637cd3386451,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/8,-s9qJ7ATP7w,8,text,3.2949869632720947,2.1999996006488804,13,768,33,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6676808209475177,1 word intervals are not reliable under the fixed-transcript CTC path,True,0.9230769230769231,-s9qJ7ATP7w__8.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-s9qJ7ATP7w__8.npz,1de4d91bd9d9fb508a3779658747da88460db0bdb1beefe7ea5b2c77edfd2239,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/8,-s9qJ7ATP7w,8,audio,3.2949869632720947,3.132287263870239,319,74,33,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9506220506437796,F0/HNR naturally undefined or unavailable in 192 rows; support masks retained,True,1.0,-s9qJ7ATP7w__8.npz::native_audio_*quality*,-s9qJ7ATP7w__8.npz::query_word_audio_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__8.npz,1de4d91bd9d9fb508a3779658747da88460db0bdb1beefe7ea5b2c77edfd2239,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/8,-s9qJ7ATP7w,8,vision,3.2949869632720947,3.2165000438690186,97,35,33,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.976179899866695,none,True,1.0,-s9qJ7ATP7w__8.npz::native_vision_*quality*,-s9qJ7ATP7w__8.npz::query_word_vision_H_time,content probe disabled,features_v2/-s9qJ7ATP7w__8.npz,1de4d91bd9d9fb508a3779658747da88460db0bdb1beefe7ea5b2c77edfd2239,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/1,-yRb-Jum7EQ,1,text,29.288021087646484,18.119996219873425,49,768,293,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.6186828453055278,none,True,1.0,-yRb-Jum7EQ__1.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-yRb-Jum7EQ__1.npz,2f16da1baa8660e5bfc608f5237cdd59725d16e57a19bc02c4556cd2c7129a46,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/1,-yRb-Jum7EQ,1,audio,29.288021087646484,28.574997466963687,2911,74,293,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9756547696223987,F0/HNR naturally undefined or unavailable in 1978 rows; support masks retained,True,1.0,-yRb-Jum7EQ__1.npz::native_audio_*quality*,-yRb-Jum7EQ__1.npz::query_word_audio_H_time,content probe disabled,features_v2/-yRb-Jum7EQ__1.npz,2f16da1baa8660e5bfc608f5237cdd59725d16e57a19bc02c4556cd2c7129a46,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/1,-yRb-Jum7EQ,1,vision,29.288021087646484,28.81599998474121,875,35,293,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9838834757222853,OpenFace confidence/success gate rejected 10 native frames,True,1.0,-yRb-Jum7EQ__1.npz::native_vision_*quality*,-yRb-Jum7EQ__1.npz::query_word_vision_H_time,content probe disabled,features_v2/-yRb-Jum7EQ__1.npz,2f16da1baa8660e5bfc608f5237cdd59725d16e57a19bc02c4556cd2c7129a46,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/5,-yRb-Jum7EQ,5,text,13.169010162353516,9.399999201297762,25,768,132,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.7137969433852901,none,True,1.0,-yRb-Jum7EQ__5.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-yRb-Jum7EQ__5.npz,6d6145293cf84d1fc8324dfa8d39465e2d7fc07affb67ff68deeabf6e3c410d9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/5,-yRb-Jum7EQ,5,audio,13.169010162353516,12.835559805905497,1305,74,132,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9746791632524319,F0/HNR naturally undefined or unavailable in 773 rows; support masks retained,True,1.0,-yRb-Jum7EQ__5.npz::native_audio_*quality*,-yRb-Jum7EQ__5.npz::query_word_audio_H_time,content probe disabled,features_v2/-yRb-Jum7EQ__5.npz,6d6145293cf84d1fc8324dfa8d39465e2d7fc07affb67ff68deeabf6e3c410d9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/5,-yRb-Jum7EQ,5,vision,13.169010162353516,13.116500854492188,394,35,132,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9960126609962351,none,True,1.0,-yRb-Jum7EQ__5.npz::native_vision_*quality*,-yRb-Jum7EQ__5.npz::query_word_vision_H_time,content probe disabled,features_v2/-yRb-Jum7EQ__5.npz,6d6145293cf84d1fc8324dfa8d39465e2d7fc07affb67ff68deeabf6e3c410d9,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/6,-yRb-Jum7EQ,6,text,11.241994857788086,5.480000853538514,19,768,113,768,fixed 0.1 s physical grid; direct native interval projection,"word-level, variable duration; fixed-transcript CTC source interval",0.487458046624363,none,True,1.0,-yRb-Jum7EQ__6.npz::native_text_*quality*,not_applicable,not_applicable,features_v2/-yRb-Jum7EQ__6.npz,b51eb9ad06de804934cb6a315de591ba9ff7f8aa71bffff7f8001ab29945d989,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/6,-yRb-Jum7EQ,6,audio,11.241994857788086,11.016339672578349,1125,74,113,74,fixed 0.1 s physical grid; direct native interval projection,native 10 ms feature step; 25 ms windows; context ranges retained separately,0.9799274783466556,F0/HNR naturally undefined or unavailable in 835 rows; support masks retained,True,1.0,-yRb-Jum7EQ__6.npz::native_audio_*quality*,-yRb-Jum7EQ__6.npz::query_word_audio_H_time,content probe disabled,features_v2/-yRb-Jum7EQ__6.npz,b51eb9ad06de804934cb6a315de591ba9ff7f8aa71bffff7f8001ab29945d989,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/6,-yRb-Jum7EQ,6,vision,11.241994857788086,11.183500289916992,336,35,113,35,fixed 0.1 s physical grid; direct native interval projection,native OpenFace frame timestamps and frame indices,0.9947967804103228,none,True,1.0,-yRb-Jum7EQ__6.npz::native_vision_*quality*,-yRb-Jum7EQ__6.npz::query_word_vision_H_time,content probe disabled,features_v2/-yRb-Jum7EQ__6.npz,b51eb9ad06de804934cb6a315de591ba9ff7f8aa71bffff7f8001ab29945d989,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 diff --git a/submit/final/output/q1/features_v2/sample_summary.csv b/submit/final/output/q1/features_v2/sample_summary.csv new file mode 100644 index 0000000..accd5c6 --- /dev/null +++ b/submit/final/output/q1/features_v2/sample_summary.csv @@ -0,0 +1,101 @@ +sample_id,video_id,clip_id,source_duration_s,source_video_sha256,feature_path,feature_sha256,sec_length,word_count,hard_aligned_word_count,unlocated_word_count,status,config_hash +-3g5yACwYnA/13,-3g5yACwYnA,13,5.5139970779418945,aeb47627f59dce3d0a85a44ef35e4a3bc18211498e98c8c11cf60269646df24f,features_v2/-3g5yACwYnA__13.npz,8ff79652816c4749ebfb19c95fb1f4826e27e19e850db23a289144985bcacb51,56,15,15,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/3,-3g5yACwYnA,3,14.388997077941895,eff8cfefba2425155f2b72656829b34b23be8bfe1dcbece384154f56818aa26d,features_v2/-3g5yACwYnA__3.npz,e3756cf59da4cc1f56b316b9af66e1afbd7f76d45ed74296ee5e239e078bce61,144,29,29,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/2,-3g5yACwYnA,2,9.394009590148926,0619c01f137d017c15c058176f18a575919189c7e81ebd2bfb993afdb86f3de7,features_v2/-3g5yACwYnA__2.npz,e84dc34a549d0d9ed9200d889c8c4a3c9e3af2cf69db297939794cee052f2ab8,94,14,14,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3g5yACwYnA/9,-3g5yACwYnA,9,8.816991806030273,b962573b12ed1f06d5533f6427c80dce2bae3a99dd332f6d3fcb32c579f3f016,features_v2/-3g5yACwYnA__9.npz,585ffb341161b798fb8213915eee94593519cf50a6510dad690d8386d2ca5f40,89,21,21,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-3nNcZdcdvU/5,-3nNcZdcdvU,5,7.867969036102295,7ea53ad502b77be7d2ee4494dc23a9478c897792203cf887d546e83d584c1728,features_v2/-3nNcZdcdvU__5.npz,a69c1bca94b0fa15c69c31eb4909874a08ffad213fb10b5d27cf3ba1cabfa13f,79,18,18,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/2,-HwX2H8Z4hY,2,3.9820311069488525,920a1052c09ebd1eedba6dd1ccfecd80f56f6f0fdcdd1f6dd32b0d90bcb89358,features_v2/-HwX2H8Z4hY__2.npz,bce95b4eaa69f6b35684b191461419ecc2083ee944ced41807e46eba8d3131c5,40,8,7,1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/5,-HwX2H8Z4hY,5,5.6529951095581055,8d61b7f5840a8bd403d9bc6c3cd49029ffc4e304cf9c7834735c1a6bb5fd369d,features_v2/-HwX2H8Z4hY__5.npz,843601e008fa1ddcf4dc530837017e5fbccfc7f6aeaa3aff1b62a336668a0b4e,57,10,9,1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/6,-HwX2H8Z4hY,6,3.3580079078674316,696576dcbd0dc42cb678fa40d8a0f0f419a3072bfa7662d653756ab29bba4c46,features_v2/-HwX2H8Z4hY__6.npz,e0f9f2edd105ce67f8269fb3a41603af01002047e25a450cf8dda1b4234b2141,34,7,7,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HwX2H8Z4hY/9,-HwX2H8Z4hY,9,7.733983993530273,90c18e627f27a12c14e21ba24c79edf3008762b4ac26874b490ad3f888381702,features_v2/-HwX2H8Z4hY__9.npz,8248b6823c7a39a16c44389a7ef71fa206492ce35ccea39ea62621c3bcce1721,78,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/1,-NFrJFQijFE,1,5.745999813079834,d8fcf16c6eb51c56947ec3d0549c6f3bc7a0cf1fc5f5d89e8b38d4249a08db4f,features_v2/-NFrJFQijFE__1.npz,1b5b76764b7a6ce5bd68eca4dc24347e3d0e800bc2abe3c6af28cca881f4f498,58,16,16,0,partial,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-NFrJFQijFE/2,-NFrJFQijFE,2,6.855999946594238,a54a5c144a64f5abd26b19aafa6cea8142ee08e9385fa508fa70c934f6b616c1,features_v2/-NFrJFQijFE__2.npz,3805d0f3ea3c570accc297ebea6af7e0cfcc6f0f100593a6b06b34b5563a237a,69,18,18,0,partial,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/12,-THoVjtIkeU,12,14.896029472351074,e2f148f4f2e74724dc13b3ce870a0ce6a06740cfc224f5a9b3d0a870b196e7d4,features_v2/-THoVjtIkeU__12.npz,7830ce91514263b5cff4c305da3cf42cb86eef37158aad86f625d79f7268ac07,149,39,39,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/2,-THoVjtIkeU,2,4.2919921875,35a6c2464ffd68dd96411edb5dc2763f0efb59f264c1d8b737e616fa6b0d1d5b,features_v2/-THoVjtIkeU__2.npz,96d2c188b9ddaa891af1dfce83e12c5ed49abc0e85d96b802a8d28ebdec4dc66,43,10,10,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-THoVjtIkeU/6,-THoVjtIkeU,6,8.097004890441895,efaa55ee6394032c867046690a92c29edfd825005f0a090cca5ccb763b227f64,features_v2/-THoVjtIkeU__6.npz,8957ebe6c0583a0dce593ce8cc42db829fdd1be8e112bcf08d99b083f5a3ac8a,81,30,30,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/1,-UuX1xuaiiE,1,10.350000381469727,d5bbda38fcec15817d2b87bab5dcc559d6d425f7d28a82e6c32d13f14b48650c,features_v2/-UuX1xuaiiE__1.npz,aecd48aa4a55db3bb21c7fadbb5b301f00b4ec235bc67a47dc5141bb32a7be9a,104,27,27,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/0,-UuX1xuaiiE,0,3.6333329677581787,f578b305d53bc152702f5e771de0fcd12d1e9aaefc5cefce3d2082a417aab7c8,features_v2/-UuX1xuaiiE__0.npz,2e24c45d36ef3c1925706d397f7c69deab53f7838a58bb66d94b5c03d09f1258,37,9,9,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/3,-UuX1xuaiiE,3,4.1300129890441895,eddb408f25bdc13e6de2fb74caeed709cb7a8f00640e7897135330437f66a2aa,features_v2/-UuX1xuaiiE__3.npz,1efdc8d158b2480f4829148cd5eabc9236175782b056575467486af004ac81c4,42,9,9,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UuX1xuaiiE/6,-UuX1xuaiiE,6,8.113997459411621,f0131ac00e44410fbd32c547a0d421c2791172434fc0203bb969abe14a530532,features_v2/-UuX1xuaiiE__6.npz,c6bf473a36b950f74aacf8e8d2f1153e7591430d4ad57da3a1adb2da79b72198,82,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-a55Q6RWvTA/3,-a55Q6RWvTA,3,22.15397071838379,15d029fc15f50b268b98f1e8abc65e45582e638c13a018d7aa74a18373275946,features_v2/-a55Q6RWvTA__3.npz,c1989fc2b6e742d75a029b7f912e0fdd73cfdbad35bcceae0e0e39ae0582b611,222,65,65,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aNfi7CP8vM/7,-aNfi7CP8vM,7,8.694987297058105,0b6389f45bb966113c65a11998c6e7facdd24c8c42acf2e8cf32e0f2139402e1,features_v2/-aNfi7CP8vM__7.npz,dcb20301dbb921eeaee67499779334261ac38cee42e04215b54bdd790eb04d71,87,18,18,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-aqamKhZ1Ec/0,-aqamKhZ1Ec,0,10.966667175292969,6f6e674bbba4353399e9e7825217f37e6d8ca1adf9675f33cf37106f31c55548,features_v2/-aqamKhZ1Ec__0.npz,686f7710f18ce27ca864c44021246fdedd4d41b4620b210b2bae988f4011561e,110,14,13,1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/1,-dxfTGcXJoc,1,16.16100311279297,b5ffdc98a4a98b8f0aae55dee5fed66a43bcb129f47a250a004c1cc74e572595,features_v2/-dxfTGcXJoc__1.npz,bbc68c9e5c1880673cfb77f48aa3dcdf10829b07cd64e6b00a8b7a3f8694b9a8,162,36,36,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/0,-dxfTGcXJoc,0,20.5,04bd0be907fb46c613f926fe1b06bc2c3b30881c293156510baa34a4105ade55,features_v2/-dxfTGcXJoc__0.npz,76423f4fa8a57e214ecbebd02bc72e06eaaa8b8e25f40cca4fdb9296a1e30673,205,41,38,3,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/2,-dxfTGcXJoc,2,16.697982788085938,46a5e523b5dd00364c99a3bcbc6fae4c80e1382a84b5b5e681c46eb074ed10f7,features_v2/-dxfTGcXJoc__2.npz,fa9effa2f4efcf00296196e2ab90d819de5c6cab85f4075d8f2ae419743f57b5,167,34,32,2,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-dxfTGcXJoc/6,-dxfTGcXJoc,6,12.735026359558105,55c1ff86729ab21da743a5107ed07b220e41b4193cd1c0ba9f009e65c3ab0513,features_v2/-dxfTGcXJoc__6.npz,28beb6292687bfd0bc6a90e97083bfb2e7eaf133447dd9bf2a9be974d5b9fc4b,128,27,27,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/26,-egA8-b7-3M,26,6.030990123748779,11f78aed680ea9c5862a15e04ff6c1f8776c46d29c98b68d28773ff39d959685,features_v2/-egA8-b7-3M__26.npz,8f752d0ae41def9d6cfa650d06674143d8d743be33a702a947d181ff39c08e59,61,15,15,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/17,-egA8-b7-3M,17,7.271028995513916,f967e81e0edd51c3700671721afb3fbecbbc32652f27d2db40ea444e8d80c68f,features_v2/-egA8-b7-3M__17.npz,d18309c6de9e1218a6d8133d9f79c2df0a4a582022c1417a8ccd7ae71fbab8ab,73,16,16,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/18,-egA8-b7-3M,18,8.386002540588379,fd4bc95a6adfb16a9588b7ed65cc2812186ce3d607804dc76f4486312951ef90,features_v2/-egA8-b7-3M__18.npz,dde916a05af47a5487e06c2749299e5bf2fc67fadb36a575c20c66065ac03176,84,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/16,-egA8-b7-3M,16,5.538021087646484,4c015f85e3bd901ecedde8c0c2ca4a756d9a77dfe4177625c481581f2037ff9c,features_v2/-egA8-b7-3M__16.npz,6baba8aa2db080c79a9b72bf3c0814e66423a9d5a56216a09ffa699bc359b104,56,11,11,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/13,-egA8-b7-3M,13,4.18398380279541,cbd627c225ebca37521ae238cb9ec254cad236822f4f37ebfbb039e6c56aea4c,features_v2/-egA8-b7-3M__13.npz,9133432494bb49a3e563cec8420925d5522ae007917972f82991886d55be47ba,42,11,11,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/1,-egA8-b7-3M,1,10.266016006469727,589a7989b1b4888568c31614a2d6beefba88fb87289c85b94df2d8051500403b,features_v2/-egA8-b7-3M__1.npz,71288235e60086aa6ab363066f68cf77c57048c65cf3a6b45f32c227381e7061,103,20,19,1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/6,-egA8-b7-3M,6,6.264974117279053,2e88a00d5e55863aa956b4b0949fc0e4f2975ccfd83b3194957f6fca86ecfce0,features_v2/-egA8-b7-3M__6.npz,0cbad1d28be25446822090f74bf6d15e4e8d3c16e03cc4c863597e457b6bfb5f,63,12,12,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/9,-egA8-b7-3M,9,5.0899739265441895,bcd643f328616312eae9b28e6c9aabbe8acaec3e4c4be58daeb45e3eee4d973c,features_v2/-egA8-b7-3M__9.npz,295b96b7d73d49dd83272d84f7b5741daa654d4323f2a003e94a21aa0726cf2a,51,10,10,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-egA8-b7-3M/20,-egA8-b7-3M,20,6.644987106323242,719ef133920e74b3ea33925d2bca0270b062199116867568f88ac83cadcd74e8,features_v2/-egA8-b7-3M__20.npz,3ef6eab499a10dd0f6181a3b4175bbcd5c4d629206e462ab642d8e2c58e689e9,67,20,20,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/3,-iRBcNs9oI8,3,5.620999813079834,39c547acd1a8da2ebc6c6ab008191a5676420398a974cd9611cad087f0ccec50,features_v2/-iRBcNs9oI8__3.npz,6843ce1b4b5a47d13b7d9cdcc6426141def4897da2ea0e7e325da54f3ce14907,57,8,8,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/7,-iRBcNs9oI8,7,4.104000091552734,cc7b1c7a06ec41d72007b5c42fb8036f30b66d5cf76e3f093160c22fc5e38081,features_v2/-iRBcNs9oI8__7.npz,d7421743e7b1804ea0bb71568d9d3972fe5a908f699443a4125291f5bb26171b,42,9,9,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/6,-iRBcNs9oI8,6,2.9030001163482666,030b0b8d8d4ae47799432ab71b66fcc881f077c5b0feef892c1f146dc0a592e3,features_v2/-iRBcNs9oI8__6.npz,d24c023d23ecd8a1715e2759f47b155006e5eb61c2e4d25e436b62af400574fe,30,5,5,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/9,-iRBcNs9oI8,9,3.4159998893737793,352bdcc73d3622d6abac1b09f2039b211d15b9bf7acee66f0b07783c9b7a4c74,features_v2/-iRBcNs9oI8__9.npz,fe85860d993e18a09afb8293bc00ac76b3ad673db783d4a158ab8062b697a2a4,35,5,5,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-iRBcNs9oI8/8,-iRBcNs9oI8,8,8.093000411987305,72a49e3da2862addcde033bd2fbae957d086533664ca4dcbccd3a851bcd50412,features_v2/-iRBcNs9oI8__8.npz,230f08e63b84fc8eea864ef801a59dbdae7b40800e58731184c2f41c70f35c09,81,21,21,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/5,-lzEya4AM_4,5,7.675000190734863,3b74de8d0e66fd754594f05985e5af3480adcc03555f79225d3b727cb4bed043,features_v2/-lzEya4AM_4__5.npz,a7c6dc3a59764e6353c1fddcaee94abe8c05335fd54803e1f5dbc5c20a21c277,77,19,19,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-lzEya4AM_4/6,-lzEya4AM_4,6,14.7919921875,ff5da5afb551001d58100ed2471caea51c741046d5396957efbf40082307ca18,features_v2/-lzEya4AM_4__6.npz,cafd2cbc61f1e95896fb3bd46a456ba384ea6f3708a659f87749f9ef7432be1b,148,43,43,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/1,-mJ2ud6oKI8,1,6.103000164031982,c8e3acb4ca08679796c4c8cdfcd8c2b3cffc0465015efaffa1eccdb55ff4f45b,features_v2/-mJ2ud6oKI8__1.npz,cd2c39bb1ca30648413e10739b052627f0b660127f461db37b3cfc93c72e8155,62,13,13,0,partial,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/2,-mJ2ud6oKI8,2,5.730999946594238,c41790f8b75f886bd1f5d6d72cbab1985306d0b715e5ae47203f3d0c8a3e7b28,features_v2/-mJ2ud6oKI8__2.npz,948618c6648a9a00753aff26cb65f3b9ef3bbe075f7e3d5a4c496eae541531dc,58,11,11,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/6,-mJ2ud6oKI8,6,2.256999969482422,134a3a4ebbc423760133b0da0e90cfe84d75ea85df3968000afd84153c5d828d,features_v2/-mJ2ud6oKI8__6.npz,ca445e27d85ecd9578d39c37d5ad93b8c6113bf264861a6945242e202cbf7e1f,23,5,5,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/9,-mJ2ud6oKI8,9,7.0329999923706055,df3442f3ed8f894b507923e87e426ae9636790a4a502d00c350a215270cc3e47,features_v2/-mJ2ud6oKI8__9.npz,1610e8626eec162785662e4445b5a8581abab6425a7956bbfe147742d06aad7c,71,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mJ2ud6oKI8/8,-mJ2ud6oKI8,8,4.191999912261963,6af6614c9838417a48c4817f4dd5bf1e6669eb5f78f983e9a68adc113fcdc7cc,features_v2/-mJ2ud6oKI8__8.npz,f262bc1e640742b43f80bec55ad965d31d2ccadfc7f5c921848ff5f41c76877a,42,11,11,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-mqbVkbCndg/0,-mqbVkbCndg,0,6.466667175292969,cdeba953bba30907814acf72f3e35582dec263eaa25bdd922dad7fafe02e4dda,features_v2/-mqbVkbCndg__0.npz,4ccb2b3e910eeb9a00598d3204a7214de9b453636d1d8c026419aeaee157a5c6,65,12,12,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/2,-t217m2on-s,2,5.6860032081604,6713a983112173204502dc5adbb886bc6c38554b4627698611931420c47663a8,features_v2/-t217m2on-s__2.npz,9f325b13ee0982de9e94c5ce7ba483a970251ba88b86aac2fb7a67189978d5b2,57,12,12,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-t217m2on-s/7,-t217m2on-s,7,17.183008193969727,41c76b8a733ecb780d56348f55d00f0ba0c07f44f46e93044e62d876710180b7,features_v2/-t217m2on-s__7.npz,9fd4771029300d09aefe043ea3ce447828c34de03d8a238abdf2f83cb6b96bc7,172,45,45,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tANM6ETl_M/3,-tANM6ETl_M,3,6.758008003234863,6d3af064c060dad3816a9e1dfa00101faebd8b7da7d8ceea85b0bc0fca70abd6,features_v2/-tANM6ETl_M__3.npz,dc3c9acd5f216c41559a001c278266aa2fe777f99cb45acd9925444c71bc84d9,68,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/11,-tPCytz4rww,11,5.466991901397705,6dc09d02467baaef67594ff32cffb01f8ed1565cda4c5abd161b8261a51e2e5d,features_v2/-tPCytz4rww__11.npz,03b82e99bc518fc778ea4447b4564b8cbb3b3d132b8316d75c65ff1e50e66769,55,17,17,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/10,-tPCytz4rww,10,4.788997173309326,758183192cbd5f0f20823d26ece3ddee40dec0aadec35247b2495510a53f52f8,features_v2/-tPCytz4rww__10.npz,11be01f908f0545b1cd3f75380c9c51a731d8b5f087b9d34f5670e02766da840,48,12,12,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/12,-tPCytz4rww,12,11.863997459411621,e3c7f9cd67ad2d20fef997696dc278361f97f027db58762e22104e5720d509d5,features_v2/-tPCytz4rww__12.npz,da94125e6235aecdfd31615210d8bb22e56b54882ced8e548c09408fa1f14191,119,26,26,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/16,-tPCytz4rww,16,7.205989837646484,8b3f82f9628792eebaf8c227becc2ebeba59bb8abb668bcca86e07fb1a992a93,features_v2/-tPCytz4rww__16.npz,5fd4724db408e31ee12e37ddf109dcaf9899521f6641db9e6ee8b144f5625b00,73,16,16,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-tPCytz4rww/18,-tPCytz4rww,18,6.644987106323242,eece70451e9c37a4b5f375bf646f66e09113fe3467f35bf1720cc14d986c1776,features_v2/-tPCytz4rww__18.npz,92bac66566bcd80659c7e96a6d47014c994bb8d815cd906be5cccab86e50b82b,67,16,16,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-vxjVxOeScU/4,-vxjVxOeScU,4,9.127017974853516,00282df9a314394f19d616d561bd1916d1d30dca4c814dfb43630ca164f4c719,features_v2/-vxjVxOeScU__4.npz,036473f43edafbd41626317599d4df6e6847def482a18c760e45f77f822b45f4,92,21,21,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wMB_hJL-3o/7,-wMB_hJL-3o,7,6.044010162353516,04a73d73fd150b07edab8e652fd14c9cb4a0f6e017ea97b17e8146e377b9fae7,features_v2/-wMB_hJL-3o__7.npz,e091d71d9bb99da782ea268fb1652ee8ad0a4b256af35f919beed8b52309000d,61,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/1,-wny0OAz3g8,1,6.844009876251221,c5535859f129ce04da5f5b5104d02d4168057b9d947a5349d423278ad4f3d8e8,features_v2/-wny0OAz3g8__1.npz,bdbd64a96b68b7472c5a6d35ec8f973eb5c28c4b8af04a3cd6f33176c0db4d41,69,23,22,1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/0,-wny0OAz3g8,0,3.3333330154418945,1cc21b4432e2f4b592284ee40558a18acc7d208019117aebdb2f6a1b67ea198d,features_v2/-wny0OAz3g8__0.npz,8985259ed866ff2f7a65967434022d2a40a9006742175ce7f7fb88cdc8513c02,34,9,9,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/3,-wny0OAz3g8,3,6.146028995513916,ea917c506bc9fa39460f2b722f7c416f646312bb17cd74baa618905b54837bce,features_v2/-wny0OAz3g8__3.npz,3bc79a7fe97c226b5cf3bddd080ec9875daf3ec94b16582e39dcddcc7f77fcfc,62,20,20,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/2,-wny0OAz3g8,2,6.686978816986084,1825fb37db2e914fb616cd80aebd613afd873eecb7edd4d0b53158bfda0e522a,features_v2/-wny0OAz3g8__2.npz,d9012975be4a0faccd98b3ed4462c664157184b13034c342ed0916667b7074f6,67,23,23,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/5,-wny0OAz3g8,5,6.9119791984558105,6c6a8a4cba654b25190ab1793bfae81c3d2159ecd6cbc02d3fcbd598543acce0,features_v2/-wny0OAz3g8__5.npz,96f35b0e59d87b842d8e3f73a9601e0c4aab770f47984d48cc4409f41b783c3f,70,20,20,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/7,-wny0OAz3g8,7,8.06796932220459,cbf9cac1048ce191aef781d0c4563eb97f9f3ddd52eb1f9551fa78d31d70e7cd,features_v2/-wny0OAz3g8__7.npz,8e08e91628357e41dbd627d334780086ae59dbc3fdc948bb0d35c9a4581bae8c,81,29,29,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-wny0OAz3g8/9,-wny0OAz3g8,9,6.2130208015441895,b76f70fe783ee76bf1b637137226c98e67d13a6b15ec725894175b36cd76a136,features_v2/-wny0OAz3g8__9.npz,12f2eed3652cdca9c0cb7ba62acd555913433a5782a9fc3bd4e3287cc390fbc7,63,20,20,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/0,-571d8cVauQ,0,5.066667079925537,defc62e1530d9b0cd27f2acfe042b71dd84d7e8c85f1df261f04912091976088,features_v2/-571d8cVauQ__0.npz,9c38a6e6628f67352d27716703f7be63cb4d958ba4ba8eaf697ba3b637aeaa42,51,12,12,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-571d8cVauQ/5,-571d8cVauQ,5,15.272981643676758,e304c03fac1f477823fe0926c4fb421b9c4e4091f85718b729afe856c24a7d3e,features_v2/-571d8cVauQ__5.npz,1397887ed11ede3e9cab0566d564fd11014bc15e24aa2a6f5675ea452c9d149c,153,43,43,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/1,-I_e4mIh0yE,1,7.622004985809326,8453a966d4bd1f0179eb86a35cd34c0864a3a3bc2fa86f37783060cc0d14f5df,features_v2/-I_e4mIh0yE__1.npz,5f2b4811b0ae11e9e00f74d0f6dfaff9ee474c13b3033793ad73cbf233a2f60e,77,18,18,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-I_e4mIh0yE/3,-I_e4mIh0yE,3,9.163021087646484,a015f02ae51d74c42ad27e2a0831263a303f0658ac8d984819e27ad61342b23e,features_v2/-I_e4mIh0yE__3.npz,692898c2d7d4f91ef25c4ec60364ee7cc7c64baff8d09a789600f6b9fb585295,92,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/10,-UacrmKiTn4,10,7.361979007720947,00312e0602dbeac970de5d7dd0e8a970e514fe45d7fe6625b4f454b299e36360,features_v2/-UacrmKiTn4__10.npz,2b9b15073d07d5a042ff7e4d89f4819d99efcc98e96b5fe2d42e26b6421ed01c,74,18,18,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UacrmKiTn4/4,-UacrmKiTn4,4,4.741015911102295,a7f2ab0d6ee2e2a2d5cd4239f03e6cac67c618ddb744891323df3ff87e29af84,features_v2/-UacrmKiTn4__4.npz,7d6be8f06193a82c27fde3e99106fcd518632cf61a19a66f567d8327057e280b,48,14,14,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/7,-hnBHBN8p5A,7,7.372000217437744,0ec6fb76226315d3fafbb483e3500e60cc8a6729617b10e4b4b76163595080fb,features_v2/-hnBHBN8p5A__7.npz,9ebd1d3b4bbb4cbb6e41ef9f67e049323c7b13a99f991d5f44fc56216fe610f6,74,13,13,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-hnBHBN8p5A/6,-hnBHBN8p5A,6,6.401000022888184,997f6808ac3973ddd71800e91c8e10755c10b5037186d84348a79e00d672fdc7,features_v2/-hnBHBN8p5A__6.npz,a778554ac2390ad44dc63d88a22d0cbcf817eec084022dbf5e48a2a3be52236f,65,13,13,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-qDkUB0GgYY/6,-qDkUB0GgYY,6,4.677995204925537,ab19bf4a761d3919b7113b1104ccaadbbf0c4e7b45694904d094280a2c329c8b,features_v2/-qDkUB0GgYY__6.npz,d157258aff14c527b21565f98c368c753ff20f31aa0cbe051f1d24c57a7412e2,47,16,16,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-uywlfIYOS8/4,-uywlfIYOS8,4,6.044987201690674,fde46f5222cf5cfec7af9c93214c972ce2d686b0bf9c90a291021601968986f9,features_v2/-uywlfIYOS8__4.npz,d07fd83e0727367f03d2ae5e0ed623da12fd1946fc010dbdd93d5148e0d4730c,61,18,18,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-6rXp3zJ3kc/8,-6rXp3zJ3kc,8,12.805012702941895,59b77a0f873b7fa95e7327b93d64bb5a4bdae84039321988ef70bba8e63ee1ae,features_v2/-6rXp3zJ3kc__8.npz,becdc042c9ef64c388bde8e5b3a98ac8d99782afcb454c5d8feab4289ab1523c,129,34,34,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/0,-9y-fZ3swSY,0,6.8333330154418945,29a1fd181293cdbe24e50edcad51e3cc0f27a5824d840bcc39d453b272fb6e3c,features_v2/-9y-fZ3swSY__0.npz,0c96186d24ab43623baf90a1e626f66f7cc1695c0b8b70ad3ca63a90a8110f52,69,22,22,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/4,-9y-fZ3swSY,4,2.8210289478302,fe04a26710e85c58bdaad5a48618c7e0f742d82b0d920eb4a046c8f14a6921dc,features_v2/-9y-fZ3swSY__4.npz,6cff9072da7459fdf9c4a0dda4c687b8b2c525ba3af893be8b4b51943cff0e88,29,9,9,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-9y-fZ3swSY/8,-9y-fZ3swSY,8,4.988996982574463,6140d031e614b62a5d8df73346fef2417c28cee3501310aa95aa40d358b4b256,features_v2/-9y-fZ3swSY__8.npz,cd54043f6a090b25e8ba26bec6b6a399b17e798ca08161fd33239be6f5761a50,50,12,12,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-AUZQgSxyPQ/2,-AUZQgSxyPQ,2,22.511003494262695,e09034b30bb4f8fb9f9c2568afc20f2a764e327fe6ef4e13f21804eaac51bb7b,features_v2/-AUZQgSxyPQ__2.npz,6b3fad600f0199347f255e07d223482885cd794a47c0d7ac2ff16c86232495d7,226,41,41,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-HeZS2-Prhc/2,-HeZS2-Prhc,2,8.261979103088379,edb0fa22fdfbd990c8174764af78670bd2ecad799240d35d96a1124a3cd9dc0c,features_v2/-HeZS2-Prhc__2.npz,a545e12987ed9776f6799e87297422df5b8ab3bacade90fcbf98bea7e28ad1c5,83,16,16,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/0,-MeTTeMJBNc,0,9.300000190734863,54e2097f7bdb455d243e72254e5af7ed16160739f2f3cfd22f57d2729ec1acbe,features_v2/-MeTTeMJBNc__0.npz,ef950e662a99c32820f5b841a570702e5d24ae392ab8d553be90c669f1abc4e1,94,21,21,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/13,-MeTTeMJBNc,13,5.430013179779053,80c5d996e5d7b61c58e1fe32d706efcaf80c8c8cddb5c37a5c66d527786c2a44,features_v2/-MeTTeMJBNc__13.npz,ac464b5b9963d9934a9e9cbadb993297b39724ffb7c9afef74f1d42600d5b65f,55,14,14,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-MeTTeMJBNc/7,-MeTTeMJBNc,7,10.51699161529541,40716dc7402fd6398038eded58ce939ec76be6afbeea346b968bf4c427088f3a,features_v2/-MeTTeMJBNc__7.npz,658af7fb69567bc1a1c6f6e26aa7f9c27354169c220ab987624997266ccf502f,106,31,31,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/11,-RfYyzHpjk4,11,5.238996982574463,96e481cd732001239639c8cb4e7f94e570925e5a15940920ffab0cec1f13f904,features_v2/-RfYyzHpjk4__11.npz,382456355758265669dff9e465e4cc02bb8c6724463a2e5967e7f8893c63b4e4,53,17,17,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/8,-RfYyzHpjk4,8,4.588996887207031,ed8c672c532b8c7d1c830c82d6e2c14fcb3bbd3dc1a86670a03a6c8e83c02f3c,features_v2/-RfYyzHpjk4__8.npz,f8217c39db4c367c69a617664528cad9b92ca28f532e755031a92214afa77377,46,17,17,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-RfYyzHpjk4/2,-RfYyzHpjk4,2,5.516016006469727,5e428ee22de58c9d6561b9b4353bef9dfb811a5841d9ab07b5bc453d697ad17f,features_v2/-RfYyzHpjk4__2.npz,63203a3e910c0e8121c88c86f50dfbbf99b316558376c589760573bb99a30162,56,25,25,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-UUCSKoHeMA/0,-UUCSKoHeMA,0,7.800000190734863,3f9792888ec8e969f61cfb6cb4de7bdf7ef8944afe0a9d2a9d13586e1f13b898,features_v2/-UUCSKoHeMA__0.npz,987dfd55d6b20aad24e7dfc246b8118d5428ec936e103b104a21e19d49b1cbdb,79,18,18,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/0,-ri04Z7vwnc,0,2.806999921798706,93021c70c8fad20ad3fded80e7fc790a2f9c96835de58a3b694c6906de42684b,features_v2/-ri04Z7vwnc__0.npz,9631590e28e7e4b4d153058cd781188ceecf7e6a4f799ea21a649dbb14b42a05,29,8,8,0,partial,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/2,-ri04Z7vwnc,2,6.0269999504089355,f5993ce3a6ee56298462c08692a96f78c7d914d7264257ed401de7d6e17fd148,features_v2/-ri04Z7vwnc__2.npz,ec4a0add30cd5eba8030fdadf54c66679192a3af67c6b35a518e277b9341f72c,61,16,16,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-ri04Z7vwnc/5,-ri04Z7vwnc,5,3.5450000762939453,2a5e4319bf592a18c8eb5117f7c7ec30c6c79528f9eac9fe4fb85bf70f277736,features_v2/-ri04Z7vwnc__5.npz,c4b36fc3e52553e01ed6e39ff7bc82d3b346f6895688a7473d059fb06ca68454,36,9,8,1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/1,-s9qJ7ATP7w,1,4.7919921875,a33e8f82a185bcb7648b523927308cdc2142e3076479ba2b45cd0e828636fc09,features_v2/-s9qJ7ATP7w__1.npz,8657717d40999eb17c2a865470cf52dff151f9db8a0b122fa058ac43f8bed0db,48,11,11,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/0,-s9qJ7ATP7w,0,6.800000190734863,f2324dbc6debe0d1e4254e6f943523373e3cbb30d8d0555413ce3a7b370a4af5,features_v2/-s9qJ7ATP7w__0.npz,301cc976bec37d768ef66b77e6ac982ca30f0837b6f4ea2afe9baa9c25e2b664,69,24,24,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/5,-s9qJ7ATP7w,5,8.561002731323242,bd35eb8de64ce1f8b27921294598f7cf5bb971255738f51d06458c7096c58eae,features_v2/-s9qJ7ATP7w__5.npz,476746914805af458c0c0ca41d9da33ec55b7bfa280018c4aec2cbd780f4f0de,86,19,19,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/4,-s9qJ7ATP7w,4,4.427018165588379,76e0672ffce5857a789b41fb10606402f7da58c33579b1032a8282f86fb35bc9,features_v2/-s9qJ7ATP7w__4.npz,734dea66c346fbdedb6db5861f502eff4a3dc46558466c8825ceb5413fa73d41,45,9,9,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/7,-s9qJ7ATP7w,7,6.966015815734863,5301f4403cf7cd299cc525d2f443392ac70fd70c3c9cd4a09406f2ba4df6734d,features_v2/-s9qJ7ATP7w__7.npz,1582ea53834aa456f77aa4384fd2e25514208ed51f53b4a99ff5e420b34cda27,70,28,28,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/6,-s9qJ7ATP7w,6,2.4749999046325684,40b684fd1b4559f0f82e72f9b47f4ce7c5e15059fe8d0d8ab112637cd3386451,features_v2/-s9qJ7ATP7w__6.npz,25f32b57e6213d05d50b9e95b33e02fbbba587cae2bcce8189d7e911e5b9fbbc,25,5,5,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-s9qJ7ATP7w/8,-s9qJ7ATP7w,8,3.2949869632720947,1de4d91bd9d9fb508a3779658747da88460db0bdb1beefe7ea5b2c77edfd2239,features_v2/-s9qJ7ATP7w__8.npz,aceb43a00ad661ebc13dbb273fb0bbff55ee0416a99ca825ee046a98aa6a0299,33,13,12,1,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/1,-yRb-Jum7EQ,1,29.288021087646484,2f16da1baa8660e5bfc608f5237cdd59725d16e57a19bc02c4556cd2c7129a46,features_v2/-yRb-Jum7EQ__1.npz,df02ec3e0707e3ae292d6ccabc0062d41f68e8dade44b0a04e7c93af52ab2a08,293,49,49,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/5,-yRb-Jum7EQ,5,13.169010162353516,6d6145293cf84d1fc8324dfa8d39465e2d7fc07affb67ff68deeabf6e3c410d9,features_v2/-yRb-Jum7EQ__5.npz,5d3083b3fcbdf232e5bed1955f4498481892a0c6f9fb74ff3c1460fa2975b3a1,132,25,25,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 +-yRb-Jum7EQ/6,-yRb-Jum7EQ,6,11.241994857788086,b51eb9ad06de804934cb6a315de591ba9ff7f8aa71bffff7f8001ab29945d989,features_v2/-yRb-Jum7EQ__6.npz,feda99761265679c062ad17106b6264a16e046a84d42fc0edc0c985c14232481,113,19,19,0,success,c1b931d6d64931209313a8b2f0e501069e2056939a15e900c1ee0762ba3c0617 diff --git a/submit/final/output/q2/attachment3_predictions.csv b/submit/final/output/q2/attachment3_predictions.csv new file mode 100644 index 0000000..7a4d126 --- /dev/null +++ b/submit/final/output/q2/attachment3_predictions.csv @@ -0,0 +1,31 @@ +case_id,predicted_class,predicted_class_name,predicted_sentiment,p_negative,p_neutral,p_positive,interval_90_lower,interval_90_upper,predictive_variance_mean_uncalibrated,within_trajectory_variance,between_trajectory_variance,predictive_mean_calibrated,predictive_variance_calibrated,beta_negative_alpha,beta_negative_beta,beta_positive_alpha,beta_positive_beta,low_information_prior_fallback,output_note +附件3_未对齐版本_01,0,negative,-2.161813497543335,0.7967485189437866,0.1071634516119957,0.0960879996418953,-2.7946219444274902,1.550757884979248,1.4611132144927979,1.4611130952835083,4.174087564479123e-08,-1.5156621932983398,1.6770378351211548,4.154862403869629,1.8028476238250732,3.133524179458618,2.8363540172576904,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_02,0,negative,-0.6189733147621155,0.3779905438423157,0.36179712414741516,0.26021236181259155,-1.2931972742080688,1.0875630378723145,0.44498664140701294,0.44498664140701294,0.0,-0.09244058281183243,0.4485733211040497,1.4110205173492432,4.546689510345459,1.3469334840774536,4.6229448318481445,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_03,2,positive,0.695109486579895,0.11934426426887512,0.2899485230445862,0.5907072424888611,-0.49461933970451355,1.5512335300445557,0.3907756507396698,0.39077427983283997,1.3719078424401232e-06,0.398899108171463,0.3993552029132843,1.0179466009140015,4.93976354598999,1.549193024635315,4.420685291290283,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_04,2,positive,0.5424688458442688,0.18944765627384186,0.3276962339878082,0.48285606503486633,-0.7178608775138855,1.2966943979263306,0.33880671858787537,0.33880671858787537,0.0,0.21264974772930145,0.3422609567642212,1.0159424543380737,4.941767692565918,1.2757951021194458,4.694083213806152,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_05,0,negative,-1.6132756471633911,0.7777170538902283,0.12479734420776367,0.09748563170433044,-2.460880994796753,0.9297983050346375,0.9528027772903442,0.9528027772903442,0.0,-1.1471410989761353,1.0553690195083618,3.1798951625823975,2.777815103530884,2.0038387775421143,3.9660396575927734,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_06,2,positive,1.075721025466919,0.029009997844696045,0.1393413245677948,0.8316486477851868,0.0,2.0461437702178955,0.43230101466178894,0.43230101466178894,0.0,0.9193391799926758,0.4604739844799042,0.8610076904296875,5.096702575683594,2.229879856109619,3.7399985790252686,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_07,2,positive,0.8112409710884094,0.06607470661401749,0.22776396572589874,0.7061613202095032,-0.1362186074256897,1.7385063171386719,0.3990931510925293,0.3990931510925293,0.0,0.5933741927146912,0.41373252868652344,0.9166243076324463,5.041085720062256,1.7580684423446655,4.2118096351623535,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_08,0,negative,-0.8177421689033508,0.5444804430007935,0.24635988473892212,0.20915962755680084,-1.6569616794586182,0.9865659475326538,0.5980508923530579,0.5980508923530579,0.0,-0.34253618121147156,0.6036122441291809,1.7663525342941284,4.191357612609863,1.348613977432251,4.621264457702637,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_09,2,positive,0.584557056427002,0.060164276510477066,0.22881989181041718,0.7110158205032349,-0.03433350846171379,1.4888426065444946,0.2824844419956207,0.2824844419956207,0.0,0.46303731203079224,0.2886645495891571,0.657180666923523,5.300529479980469,1.351650595664978,4.618227481842041,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_10,1,neutral,0.0,0.19817937910556793,0.4307863712310791,0.371034175157547,-0.5964999794960022,1.0180858373641968,0.2146325260400772,0.21463251113891602,1.7206826186111357e-08,0.11066635698080063,0.21938827633857727,0.8156828284263611,5.142027378082275,1.030116081237793,4.939762115478516,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_11,2,positive,0.4067777991294861,0.18321716785430908,0.36699020862579346,0.4497925639152527,-0.5481101274490356,1.093954086303711,0.23209431767463684,0.2320941984653473,1.2958071238244884e-07,0.1593105047941208,0.23480401933193207,0.7960365414619446,5.161673545837402,1.0298019647598267,4.9400763511657715,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_12,2,positive,0.7418312430381775,0.16085933148860931,0.25514480471611023,0.5839958190917969,-0.7832371592521667,1.5990359783172607,0.48600462079048157,0.48600462079048157,0.0,0.3795293867588043,0.49588483572006226,1.2342653274536133,4.723444938659668,1.633910059928894,4.335968017578125,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_13,2,positive,0.9053856730461121,0.22559532523155212,0.2667962610721588,0.5076084136962891,-1.2298040390014648,1.722484827041626,0.7403188347816467,0.7403188347816467,0.0,0.3018410801887512,0.7457730174064636,1.6682190895080566,4.289491176605225,1.9262142181396484,4.04366397857666,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_14,0,negative,-0.7449327111244202,0.4758836627006531,0.29052573442459106,0.23359058797359467,-1.5283849239349365,1.0474128723144531,0.5521396398544312,0.5521395802497864,2.380643060462262e-08,-0.2323387712240219,0.5541964173316956,1.6363940238952637,4.321316242218018,1.361243486404419,4.6086344718933105,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_15,2,positive,1.3370250463485718,0.023975681513547897,0.10470940917730331,0.8713149428367615,0.0,2.2798967361450195,0.47987255454063416,0.47987255454063416,0.0,1.167418360710144,0.5250018835067749,1.044642686843872,4.913067817687988,2.695021390914917,3.2748568058013916,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_16,2,positive,1.7538543939590454,0.012006768025457859,0.06832415610551834,0.9196690917015076,0.0,2.5877037048339844,0.47664394974708557,0.47664394974708557,0.0,1.5816236734390259,0.5373051762580872,1.0920591354370117,4.8656511306762695,3.43656849861145,2.5333096981048584,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_17,2,positive,0.6437320113182068,0.11531270295381546,0.28419703245162964,0.6004902720451355,-0.42875346541404724,1.4985958337783813,0.3550581634044647,0.35505813360214233,1.3726113579082266e-08,0.38549983501434326,0.3624156415462494,0.9383652806282043,5.019345283508301,1.4581032991409302,4.511775016784668,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_18,2,positive,0.5576586127281189,0.10332871973514557,0.2823880910873413,0.6142831444740295,-0.2905426323413849,1.4048357009887695,0.29468634724617004,0.29468634724617004,0.0,0.3613300621509552,0.29980164766311646,0.7871065735816956,5.1706037521362305,1.303192138671875,4.666686058044434,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_19,0,negative,-0.6961055397987366,0.5107713937759399,0.2870360016822815,0.20219261944293976,-1.5002697706222534,0.8932285904884338,0.47930681705474854,0.47930681705474854,0.0,-0.2732691168785095,0.4835101366043091,1.5491523742675781,4.408557891845703,1.2319159507751465,4.737962245941162,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_20,2,positive,0.5754238963127136,0.19199968874454498,0.35681214928627014,0.45118817687034607,-0.7409619092941284,1.3107733726501465,0.3477743864059448,0.3477743864059448,0.0,0.202140673995018,0.3519274592399597,1.040464162826538,4.917246341705322,1.3352046012878418,4.634673595428467,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_21,2,positive,0.7424408197402954,0.031176764518022537,0.17010530829429626,0.7987179756164551,0.0,1.706007719039917,0.32180947065353394,0.321806401014328,3.0546812013199087e-06,0.6466689705848694,0.33276495337486267,0.6083263158798218,5.34938383102417,1.6308528184890747,4.339025497436523,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_22,2,positive,0.9128319621086121,0.3136769235134125,0.24424688518047333,0.4420761466026306,-1.5561679601669312,1.679451823234558,0.9327147603034973,0.9327132105827332,1.59684725531406e-06,0.1216338723897934,0.9247239232063293,1.9581161737442017,3.999594211578369,1.9394835233688354,4.030395030975342,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_23,2,positive,1.1373980045318604,0.017837069928646088,0.10193044692277908,0.8802324533462524,0.0,2.1167213916778564,0.40735018253326416,0.4073500633239746,1.3645041008203407e-07,1.0282129049301147,0.4355928599834442,0.7500343322753906,5.207675933837891,2.3408405780792236,3.629037857055664,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_24,2,positive,0.7078329920768738,0.12042908370494843,0.2849310338497162,0.5946398973464966,-0.49775230884552,1.5678426027297974,0.39866694808006287,0.39866626262664795,6.773137215532188e-07,0.40837085247039795,0.40745168924331665,1.016898274421692,4.940811634063721,1.5724689960479736,4.397408962249756,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_25,2,positive,0.5815068483352661,0.12130436301231384,0.3121408522129059,0.566554844379425,-0.4213324785232544,1.4042198657989502,0.3172762989997864,0.3172754645347595,8.146940331243968e-07,0.32919952273368835,0.3226009011268616,0.8835678696632385,5.0741424560546875,1.3444410562515259,4.625437259674072,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_26,2,positive,0.5501649975776672,0.056601475924253464,0.24990615248680115,0.6934923529624939,-0.012923063710331917,1.4388282299041748,0.2632901668548584,0.2632899284362793,2.120857800491649e-07,0.4325553774833679,0.26744163036346436,0.5925376415252686,5.365172863006592,1.2903460264205933,4.679532051086426,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_27,2,positive,1.4617162942886353,0.016829069703817368,0.09628183394670486,0.8868891000747681,0.0,2.379847764968872,0.48261168599128723,0.48261168599128723,0.0,1.291656255722046,0.530409574508667,0.9833105206489563,4.974400043487549,2.916853666305542,3.0530247688293457,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_28,2,positive,1.010158896446228,0.024440867826342583,0.12888681888580322,0.8466722965240479,0.0,1.9912506341934204,0.39680054783821106,0.3968004584312439,7.125536427565748e-08,0.8896118998527527,0.42043906450271606,0.76673823595047,5.190971851348877,2.113187074661255,3.8566908836364746,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_29,2,positive,1.7028013467788696,0.013574469834566116,0.07568830251693726,0.9107372760772705,0.0,2.5532665252685547,0.48900747299194336,0.48900729417800903,1.7003151242533932e-07,1.5236588716506958,0.5494855046272278,1.1064831018447876,4.851227283477783,3.3458335399627686,2.62404465675354,False,unlabeled attachment-3 case; no accuracy/F1 is defined +附件3_未对齐版本_30,2,positive,0.9856576919555664,0.045877669006586075,0.16616477072238922,0.7879575490951538,0.0,1.9468833208084106,0.43973156809806824,0.4397314190864563,1.4706266426856018e-07,0.7972231507301331,0.4657270610332489,0.9605550169944763,4.99715518951416,2.0706770420074463,3.8992011547088623,False,unlabeled attachment-3 case; no accuracy/F1 is defined diff --git a/submit/final/output/q3/ati_ho/attachment4_explanations.csv b/submit/final/output/q3/ati_ho/attachment4_explanations.csv new file mode 100644 index 0000000..d129e93 --- /dev/null +++ b/submit/final/output/q3/ati_ho/attachment4_explanations.csv @@ -0,0 +1,21 @@ +case_id,fixed_target_class,fixed_runner_up_class,full_logit_margin,baseline_r_negative,baseline_r_positive,analytic_vs_exact_shapley_max_abs,analytic_vs_exact_shapley_all_pass,exact_intensity_shapley_sum,intensity_full_minus_empty_coalition,intensity_shapley_efficiency_residual,coordinate_mode,physical_time_alignment,G_T_logit_negative,G_T_logit_neutral,G_T_logit_positive,G_T_r_negative,G_T_r_positive,analytic_class_shapley_T,exact_class_shapley_T,exact_intensity_shapley_T,G_A_logit_negative,G_A_logit_neutral,G_A_logit_positive,G_A_r_negative,G_A_r_positive,analytic_class_shapley_A,exact_class_shapley_A,exact_intensity_shapley_A,G_V_logit_negative,G_V_logit_neutral,G_V_logit_positive,G_V_r_negative,G_V_r_positive,analytic_class_shapley_V,exact_class_shapley_V,exact_intensity_shapley_V,G_TA_logit_negative,G_TA_logit_neutral,G_TA_logit_positive,G_TA_r_negative,G_TA_r_positive,G_TV_logit_negative,G_TV_logit_neutral,G_TV_logit_positive,G_TV_r_negative,G_TV_r_positive,G_AV_logit_negative,G_AV_logit_neutral,G_AV_logit_positive,G_AV_r_negative,G_AV_r_positive,baseline_logit_negative,full_parameter_logit_negative,baseline_logit_neutral,full_parameter_logit_neutral,baseline_logit_positive,full_parameter_logit_positive,full_parameter_r_negative,full_parameter_r_positive +01,neutral,positive,0.17453938722610474,-0.03336911275982857,-0.02806602418422699,1.5522042984272844e-08,True,-1.478951811790466,-1.4789518117904663,2.220446049250313e-16,relative_progress,False,-0.904476523399353,0.7555410265922546,0.1489354968070984,-0.3240058124065399,-0.23826012015342712,0.6066055297851562,0.6066055142631133,-1.2055534323056538,-0.07994242757558823,-0.13918815553188324,0.21913057565689087,-0.32910090684890747,-0.25575292110443115,-0.3583187460899353,-0.3583187318096558,-0.09216936429341632,-0.06264422088861465,-0.0008340037311427295,0.06347822397947311,-0.3798588216304779,-0.5035299062728882,-0.06431222707033157,-0.06431221279005209,-0.18122901519139606,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.0520095825195312,-0.003916915971785784,0.6116019487380981,0.005518266931176186,0.4370625615119934,-1.0663347244262695,-1.025609016418457 +02,positive,neutral,0.6727461628615856,-0.03336911275982857,-0.02806602418422699,5.5258472686503524e-08,True,-0.40346074104309076,-0.4034607410430908,5.551115123125783e-17,relative_progress,False,-0.6345188021659851,-0.13300542533397675,0.7675241827964783,-0.2942814826965332,-0.10120987892150879,0.9005296230316162,0.9005296782900889,0.5349497596422831,-0.060333430767059326,-0.038159824907779694,0.09849324822425842,-0.2349826991558075,-0.2913568913936615,0.13665306568145752,0.13665306878586608,-0.1772233446439107,0.12454855442047119,0.12466159462928772,-0.2492101490497589,-0.07290247082710266,-0.16126064956188202,-0.37387174367904663,-0.3738717480252186,-0.7611871560414631,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-0.5752502083778381,-0.003916915971785784,-0.05042056366801262,0.005518266931176186,0.622325599193573,-0.6355358362197876,-0.5818934440612793 +03,negative,neutral,0.2300729900598526,-0.03336911275982857,-0.02806602418422699,5.6965897499150486e-08,True,-2.6271680593490596,-2.62716805934906,4.440892098500626e-16,relative_progress,False,0.6437126398086548,0.233465313911438,-0.877177894115448,-0.0156564861536026,-0.19630727171897888,0.4102473258972168,0.4102472689313193,-2.6401662031809487,-0.044556912034749985,-0.10961116850376129,0.15416809916496277,-0.21152648329734802,-0.20100197196006775,0.06505425274372101,0.0650542665583392,0.0028151472409566197,-0.24782779812812805,-0.003628835082054138,0.251456618309021,-0.21738770604133606,-0.18694338202476501,-0.2441989630460739,-0.24419895295674598,0.010182996590932206,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,0.34638139605522156,-0.003916915971785784,0.11630840599536896,0.005518266931176186,-0.4660349488258362,-0.47793978452682495,-0.6123186349868774 +04,negative,positive,0.7093599438667297,-0.03336911275982857,-0.02806602418422699,1.0337680578231812e-07,True,-2.610314011573791,-2.6103140115737915,4.440892098500626e-16,relative_progress,False,1.1683094501495361,-0.7724652290344238,-0.3958442211151123,0.14337058365345,0.0068162246607244015,1.5641536712646484,1.5641535678878427,-2.699520587921142,-0.11619937419891357,-0.15902423858642578,0.27522361278533936,-0.3240225911140442,-0.2022765576839447,-0.39142298698425293,-0.39142295625060797,0.043889820575714104,-0.13935381174087524,-0.17419832944869995,0.3135521411895752,-0.2877661883831024,-0.16212013363838196,-0.45290595293045044,-0.4529058923944831,0.045316755771636956,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,0.9078097343444824,-0.003916915971785784,-1.109604835510254,0.005518266931176186,0.19844979047775269,-0.5017873048782349,-0.3856464624404907 +05,positive,neutral,1.5049121379852295,-0.03336911275982857,-0.02806602418422699,8.133550488675922e-08,True,-0.4806082248687743,-0.4806082248687744,1.1102230246251565e-16,relative_progress,False,-1.709882140159607,0.5530490875244141,1.1568331718444824,-0.35266321897506714,-0.29165583848953247,0.6037840843200684,0.6037841221938529,-0.2102790276209513,-0.05014742910861969,-0.11470775306224823,0.16485518217086792,-0.22012154757976532,-0.2014065831899643,0.27956295013427734,0.27956292840341723,-0.14488289753595987,-0.21467027068138123,-0.19872985780239105,0.4134001135826111,-0.24117706716060638,-0.1745043247938156,0.6121299862861633,0.6121299049506584,-0.1254462997118632,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.9796464443206787,-0.003916915971785784,0.2356945276260376,0.005518266931176186,1.740606665611267,-0.8473309278488159,-0.695632815361023 +06,positive,neutral,1.9159240424633026,-0.03336911275982857,-0.02806602418422699,7.388492426207982e-08,True,-0.2154364585876466,-0.21543645858764648,-1.1102230246251565e-16,relative_progress,False,-1.2203913927078247,-0.4495927691459656,1.669984221458435,-0.37521523237228394,0.019710635766386986,2.119576930999756,2.1195768682907024,0.659255842367808,-0.12370359897613525,0.02123902551829815,0.10246457159519196,-0.13953426480293274,-0.16336387395858765,0.08122554421424866,0.0812254703293244,-0.1013184388478597,0.015853652730584145,0.1392299085855484,-0.155083566904068,-0.013930173590779305,-0.14624570310115814,-0.2943134903907776,-0.29431344879170257,-0.7733738621075948,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.3331878185272217,-0.003916915971785784,-0.2930407226085663,0.005518266931176186,1.6228833198547363,-0.5620487928390503,-0.31796497106552124 +07,positive,neutral,1.3011534810066223,-0.03336911275982857,-0.02806602418422699,5.65002362673539e-08,True,-0.723442018032074,-0.723442018032074,0.0,relative_progress,False,-1.6689116954803467,0.3794674873352051,1.289444088935852,-0.42971712350845337,-0.2734326720237732,0.909976601600647,0.9099765451004107,-0.1858192980289459,-0.0658918172121048,-0.08562538027763367,0.15151719748973846,-0.22639398276805878,-0.3063961863517761,0.23714257776737213,0.2371425957729419,-0.20845401287078855,-0.12855705618858337,-0.008021022193133831,0.13657806813716888,-0.4119300842285156,-0.48094600439071655,0.1445990949869156,0.14459909809132415,-0.3291687071323395,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.8683069944381714,-0.003916915971785784,0.2819041609764099,0.005518266931176186,1.5830576419830322,-1.1014103889465332,-1.0888409614562988 +08,positive,neutral,1.6082637459039688,-0.03336911275982857,-0.02806602418422699,1.0492901014735878e-07,True,-0.5725139379501343,-0.5725139379501343,0.0,relative_progress,False,-1.888288974761963,0.20506727695465088,1.6832218170166016,-0.439189612865448,-0.17151769995689392,1.4781545400619507,1.4781544351329405,0.06195046504338582,-0.041887879371643066,-0.052495844662189484,0.09438371658325195,-0.14891745150089264,-0.11223925650119781,0.14687955379486084,0.1468795457233985,0.1037632425626119,-0.04141402989625931,0.03380971401929855,0.007604313548654318,-0.35752567648887634,-0.525276780128479,-0.02620540000498295,-0.0262054322908322,-0.738227645556132,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.9765374660491943,-0.003916915971785784,0.18246422708034515,0.005518266931176186,1.790727972984314,-0.9790017604827881,-0.8370997309684753 +09,negative,positive,4.036897420883179,-0.03336911275982857,-0.02806602418422699,3.5235037376679657e-07,True,-2.9891992807388306,-2.9891992807388306,0.0,relative_progress,False,2.800884246826172,-1.3056433200836182,-1.4952411651611328,0.5610975623130798,0.024273857474327087,4.296125411987305,4.2961257643376785,-2.801382025082906,-0.038079407066106796,-0.1371668577194214,0.17524628341197968,-0.26829323172569275,-0.14377743005752563,-0.21332569420337677,-0.213325595172743,0.2530961434046427,-0.04993594437837601,0.0644339919090271,-0.01449805311858654,-0.24577169120311737,-0.3168761730194092,-0.03543788939714432,-0.03543773448715607,-0.4409133990605672,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,2.7079226970672607,-0.003916915971785784,-1.3822932243347168,0.005518266931176186,-1.328974723815918,0.013663536868989468,-0.4644457697868347 +10,negative,positive,1.9884863495826721,-0.03336911275982857,-0.02806602418422699,9.53053431729245e-08,True,-2.8972601890563965,-2.8972601890563965,0.0,relative_progress,False,1.9594717025756836,-1.0742477178573608,-0.8852239847183228,0.3760708272457123,0.06034252047538757,2.844695568084717,2.84469566339006,-2.984575629234314,-0.1273432970046997,-0.04922483488917351,0.17656812071800232,-0.16357895731925964,-0.07018119096755981,-0.303911417722702,-0.3039113522196809,0.034755587577819824,-0.13882485032081604,-0.26418352127075195,0.4030084013938904,-0.2881527543067932,-0.1471811681985855,-0.5418332815170288,-0.5418332458163301,0.052559852600097656,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,1.6883571147918701,-0.003916915971785784,-1.391573190689087,0.005518266931176186,-0.300129234790802,-0.10903000831604004,-0.18508586287498474 +11,negative,positive,1.2935872972011566,-0.03336911275982857,-0.02806602418422699,8.195638656616211e-08,True,-2.848456025123596,-2.848456025123596,0.0,relative_progress,False,1.1382856369018555,-0.7049045562744141,-0.43338102102279663,0.16562658548355103,-0.005292731337249279,1.5716667175292969,1.5716666355729103,-2.8356826305389404,0.015153793618083,-0.04410065710544586,0.02894686535000801,-0.22009000182151794,-0.24944591522216797,-0.01379307173192501,-0.013793108053505419,-0.010202229022979736,-0.13108593225479126,0.00835040770471096,0.12273551523685455,-0.08660280704498291,-0.09405757486820221,-0.2538214325904846,-0.253821425139904,-0.0025711655616760254,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,1.017406940460205,-0.003916915971785784,-0.7445716857910156,0.005518266931176186,-0.27618035674095154,-0.1744353473186493,-0.3768622875213623 +12,negative,neutral,1.9524167478084564,-0.03336911275982857,-0.02806602418422699,1.8114224076271057e-07,True,-2.605541944503784,-2.605541944503784,0.0,relative_progress,False,1.6268107891082764,-0.4549051523208618,-1.171905755996704,0.12826871871948242,-0.14643552899360657,2.0817160606384277,2.081715879496187,-2.024578471978505,-0.01550658605992794,0.08049377799034119,-0.0649871975183487,-0.22043946385383606,-0.1405203640460968,-0.09600036591291428,-0.09600043902173638,-0.5963235100110372,-0.03571124002337456,-0.003442181274294853,0.03915341943502426,-0.3830249011516571,-0.5144790410995483,-0.032269060611724854,-0.03226907039061189,0.015360037485758468,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,1.5706462860107422,-0.003916915971785784,-0.38177046179771423,0.005518266931176186,-1.1922211647033691,-0.5085647702217102,-0.8295010328292847 +13,positive,neutral,0.03907114267349243,-0.03336911275982857,-0.02806602418422699,3.290673100675434e-08,True,-0.6098982095718384,-0.6098982095718384,0.0,relative_progress,False,-1.2801198959350586,0.8114702105522156,0.468649685382843,-0.2939906418323517,-0.38156017661094666,-0.34282052516937256,-0.3428205431749423,-1.0097482800483704,0.023065369576215744,-0.03675944358110428,0.013694070279598236,-0.1403023600578308,-0.20620891451835632,0.050453513860702515,0.05045352938274542,0.21370625495910645,-0.15847381949424744,-0.0817645788192749,0.24023841321468353,-0.1458529531955719,-0.2810816764831543,0.32200300693511963,0.3220029740283886,0.18614381551742554,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.4204747676849365,-0.003916915971785784,0.689029335975647,0.005518266931176186,0.7281004786491394,-0.6135150790214539,-0.8969167470932007 +14,positive,neutral,0.17552608251571655,-0.03336911275982857,-0.02806602418422699,3.787378477504433e-08,True,-0.5350744724273682,-0.5350744724273682,0.0,relative_progress,False,-1.025192379951477,0.941837728023529,0.083354651927948,-0.2702452540397644,-0.3983325660228729,-0.858483076095581,-0.8584830382217963,-1.0336409012476602,-0.14746510982513428,-0.10266011953353882,0.2501252293586731,-0.3350585103034973,-0.25513797998428345,0.3527853488922119,0.3527853271613518,0.2199622591336568,-0.29058393836021423,-0.19060233235359192,0.48118627071380615,-0.23630091547966003,-0.09704461693763733,0.6717885732650757,0.6717886111388603,0.2786041696866353,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.4681880474090576,-0.003916915971785784,0.6446583271026611,0.005518266931176186,0.8201844096183777,-0.8749737739562988,-0.7785811424255371 +15,positive,neutral,3.1091278791427612,-0.03336911275982857,-0.02806602418422699,1.6577541828155518e-07,True,-0.3441582918167114,-0.3441582918167114,0.0,relative_progress,False,-1.7186782360076904,-0.38246893882751465,2.101147174835205,-0.5896317958831787,-0.02962455153465271,2.4836161136627197,2.4836159478873014,-0.021669268608093258,-0.14169877767562866,-0.11777668446302414,0.2594754695892334,-0.3810732960700989,-0.16558730602264404,0.37725216150283813,0.3772521745413542,-0.12142491340637207,-0.25492432713508606,0.008049803785979748,0.24687449634075165,-0.34001070261001587,-0.2736431062221527,0.23882469534873962,0.23882469348609447,-0.20106410980224607,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-2.1202478408813477,-0.003916915971785784,-0.4961127042770386,0.005518266931176186,2.6130151748657227,-1.3440849781036377,-0.49692100286483765 +16,negative,neutral,4.228886842727661,-0.03336911275982857,-0.02806602418422699,1.5444432695937982e-07,True,-3.0126639604568477,-3.012663960456848,4.440892098500626e-16,relative_progress,False,2.9560515880584717,-1.2988104820251465,-1.657240867614746,0.566375195980072,0.0027166109066456556,4.254861831665039,4.254861882111678,-2.9760817686716714,-0.02732587233185768,-0.04761290177702904,0.07493877410888672,-0.11062884330749512,-0.1420450508594513,0.020287029445171356,0.020286875000844397,-0.011243383089701336,-0.03721527382731438,0.008017238229513168,0.02919803373515606,-0.3774202764034271,-0.44900593161582947,-0.045232512056827545,-0.04523256033038099,-0.02533880869547525,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,2.886563777923584,-0.003916915971785784,-1.3423230648040771,0.005518266931176186,-1.5475858449935913,0.0449569895863533,-0.6164003610610962 +17,positive,neutral,3.5790738463401794,-0.03336911275982857,-0.02806602418422699,1.2231369783677337e-07,True,-0.19588220119476318,-0.19588220119476318,0.0,relative_progress,False,-1.7191108465194702,-0.765514075756073,2.4846248626708984,-0.5405866503715515,0.05220368504524231,3.250138998031616,3.250139120345314,0.26819344361623126,-0.06238323450088501,0.11898847669363022,-0.05660523474216461,-0.19090893864631653,-0.1367032825946808,-0.17559370398521423,-0.1755937902877728,-0.5605612794558207,-0.27668532729148865,-0.10920405387878418,0.3858893811702728,-0.134759321808815,-0.17871713638305664,0.495093435049057,0.49509339344998193,0.09648563464482625,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-2.0631260871887207,-0.003916915971785784,-0.7596465945243835,0.005518266931176186,2.819427251815796,-0.8996240496635437,-0.2912827730178833 +18,neutral,negative,0.24999400973320007,-0.03336911275982857,-0.02806602418422699,1.2262413889851942e-08,True,-1.4789518117904663,-1.4789518117904663,0.0,relative_progress,False,0.28653019666671753,0.403386652469635,-0.6899168491363525,-0.019597142934799194,-0.21449951827526093,0.11685645580291748,0.11685644571358958,-0.7159723242123921,-0.0920357033610344,-0.11092132329940796,0.20295703411102295,-0.2633468806743622,-0.1888737976551056,-0.018885619938373566,-0.018885626302411158,-0.04700716336568196,0.007763783447444439,0.15875737369060516,-0.16652116179466248,-0.2628101110458374,-0.28852805495262146,0.15099358558654785,0.15099359784896174,-0.7159723242123921,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,0.19731178879737854,-0.003916915971785784,0.4473057985305786,0.005518266931176186,-0.6479626893997192,-0.5791232585906982,-0.7199673652648926 +19,positive,negative,0.12766507267951965,-0.03336911275982857,-0.02806602418422699,2.7939677238464355e-09,True,-0.08535563945770264,-0.08535563945770264,0.0,relative_progress,False,0.7118504047393799,-0.72557532787323,0.013724908232688904,-0.0308525450527668,-0.001771043986082077,-0.6981254816055298,-0.6981254825368524,-1.2033302386601765,-0.041151344776153564,0.06523413956165314,-0.02408279851078987,-0.1305142492055893,-0.05948138236999512,0.017068546265363693,0.017068549059331417,-0.4989778598149618,-0.2795937955379486,-0.23906968533992767,0.5186634659767151,-0.14898139238357544,-0.05279204994440079,0.7982572317123413,0.7982572307810187,1.6169524590174356,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,0.3861587941646576,-0.003916915971785784,-0.9033277034759521,0.005518266931176186,0.5138238668441772,-0.3437173068523407,-0.14211049675941467 +20,positive,neutral,1.7363310009241104,-0.03336911275982857,-0.02806602418422699,4.4393042741841526e-08,True,-0.40033042430877674,-0.40033042430877686,1.1102230246251565e-16,relative_progress,False,-1.9999498128890991,0.14454472064971924,1.8554052114486694,-0.46275147795677185,-0.2674782872200012,1.7108604907989502,1.7108604659636812,1.316787560780843,0.05088898167014122,-0.08756403625011444,0.03667505830526352,-0.10067225992679596,-0.0886862576007843,0.12423909455537796,0.12423905016233522,-1.1897882024447122,-0.020659327507019043,0.06443151831626892,-0.04377218708395958,-0.28119325637817383,-0.19312873482704163,-0.1082037091255188,-0.10820371254036823,-0.5273297826449076,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,-0.004946508444845676,-1.9746668338775635,-0.003916915971785784,0.11749528348445892,0.005518266931176186,1.8538262844085693,-0.8779861330986023,-0.5773593187332153 diff --git a/submit/final/output/q3/ati_ho/attachment4_local_evidence.csv b/submit/final/output/q3/ati_ho/attachment4_local_evidence.csv new file mode 100644 index 0000000..f764140 --- /dev/null +++ b/submit/final/output/q3/ati_ho/attachment4_local_evidence.csv @@ -0,0 +1,601 @@ +case_id,modality,relative_bin,relative_position_start,relative_position_end,local_owen_margin_contribution,owen_standard_error,permutations,stopping_status,physical_time_alignment +01,text,0,0.0,0.1,0.15501678062719293,0.02824737907762575,64,max_permutations,False +01,text,1,0.1,0.2,0.3393877267371863,0.03891857387116652,64,max_permutations,False +01,text,2,0.2,0.3,0.13160342909395695,0.021407469220121024,64,max_permutations,False +01,text,3,0.3,0.4,0.1541730676253792,0.023352077620926736,64,max_permutations,False +01,text,4,0.4,0.5,0.1187975586799439,0.02708052178375021,64,max_permutations,False +01,text,5,0.5,0.6,-0.32819975397433154,0.021070444987702094,64,max_permutations,False +01,text,6,0.6,0.7,-0.18518312580999918,0.01611749256140915,64,max_permutations,False +01,text,7,0.7,0.8,-0.09827927622245625,0.015110065326582888,64,max_permutations,False +01,text,8,0.8,0.9,0.041710176767082885,0.01712487503973442,64,max_permutations,False +01,text,9,0.9,1.0,0.27757893287343904,0.04676788039390862,64,max_permutations,False +01,audio,0,0.0,0.1,-0.011243712127907202,0.009233671019475894,64,max_permutations,False +01,audio,1,0.1,0.2,-0.032589772396022454,0.008617888532936106,64,max_permutations,False +01,audio,2,0.2,0.3,-0.06452517947764136,0.012695545986241358,64,max_permutations,False +01,audio,3,0.3,0.4,-0.058146502036834136,0.012725619090246807,64,max_permutations,False +01,audio,4,0.4,0.5,-0.04902930595562793,0.01048247600797557,64,max_permutations,False +01,audio,5,0.5,0.6,-0.039003983518341556,0.009180882634066611,64,max_permutations,False +01,audio,6,0.6,0.7,-0.009039346128702164,0.005976381690355603,64,max_permutations,False +01,audio,7,0.7,0.8,-0.05669806583318859,0.012973192997537856,64,max_permutations,False +01,audio,8,0.8,0.9,-0.05080851863021962,0.011617200421893223,64,max_permutations,False +01,audio,9,0.9,1.0,0.012765651888912544,0.004659085312381929,64,max_permutations,False +01,vision,0,0.0,0.1,-0.006502474949229509,0.0015052195559562167,64,max_permutations,False +01,vision,1,0.1,0.2,-0.006554739462444559,0.0011094309519753375,64,max_permutations,False +01,vision,2,0.2,0.3,-0.011689859733451158,0.0013052881660502993,64,max_permutations,False +01,vision,3,0.3,0.4,-0.026898140262346715,0.0023536208796073924,64,max_permutations,False +01,vision,4,0.4,0.5,-0.008135400246828794,0.0009906486108777516,64,max_permutations,False +01,vision,5,0.5,0.6,0.005125108757056296,0.0010578993554463802,64,max_permutations,False +01,vision,6,0.6,0.7,-0.004117644391953945,0.0013342637103614845,64,max_permutations,False +01,vision,7,0.7,0.8,0.014664946414995939,0.0015623694916137292,64,max_permutations,False +01,vision,8,0.8,0.9,-0.006133872899226844,0.0012644093533939029,64,max_permutations,False +01,vision,9,0.9,1.0,-0.014070135744987056,0.001974886787411523,64,max_permutations,False +02,text,0,0.0,0.1,-0.2435244449879974,0.03784503512777638,16,stable,False +02,text,1,0.1,0.2,0.08069909503683448,0.02251316758096149,16,stable,False +02,text,2,0.2,0.3,0.13866681058425456,0.06606381171215224,16,stable,False +02,text,3,0.3,0.4,0.3282391676912084,0.109628547483824,16,stable,False +02,text,4,0.4,0.5,0.4153550040209666,0.12304339544019145,16,stable,False +02,text,5,0.5,0.6,0.3692910491954535,0.11042240368777048,16,stable,False +02,text,6,0.6,0.7,0.0652436160016805,0.05736249030197223,16,stable,False +02,text,7,0.7,0.8,0.025608156574890018,0.03900034766287585,16,stable,False +02,text,8,0.8,0.9,-0.19390656682662666,0.06900286960562475,16,stable,False +02,text,9,0.9,1.0,-0.08514219988137484,0.034105645498513665,16,stable,False +02,audio,0,0.0,0.1,-0.025586919859051704,0.006379168684057744,16,stable,False +02,audio,1,0.1,0.2,0.016373574268072844,0.008831531476611865,16,stable,False +02,audio,2,0.2,0.3,0.05392544995993376,0.017164153137310234,16,stable,False +02,audio,3,0.3,0.4,0.01686276914551854,0.009908323474383376,16,stable,False +02,audio,4,0.4,0.5,0.014328758406918496,0.011732993410349142,16,stable,False +02,audio,5,0.5,0.6,0.04691262322012335,0.018712270387235944,16,stable,False +02,audio,6,0.6,0.7,0.003989448130596429,0.008562717075729453,16,stable,False +02,audio,7,0.7,0.8,0.016533869318664074,0.01057255051161289,16,stable,False +02,audio,8,0.8,0.9,0.01014680229127407,0.008664929199207605,16,stable,False +02,audio,9,0.9,1.0,-0.016833303146995604,0.0029446053018819273,16,stable,False +02,vision,0,0.0,0.1,-0.0151264633750543,0.01664132360117765,16,stable,False +02,vision,1,0.1,0.2,-0.06713183771353215,0.027702583825160908,16,stable,False +02,vision,2,0.2,0.3,-0.0959150152048096,0.028765221253865606,16,stable,False +02,vision,3,0.3,0.4,-0.05922563676722348,0.025965331517317503,16,stable,False +02,vision,4,0.4,0.5,-0.04631277953740209,0.023417126665055382,16,stable,False +02,vision,5,0.5,0.6,-0.015756300650537014,0.004390764367608424,16,stable,False +02,vision,6,0.6,0.7,-0.03570611774921417,0.01888455822860072,16,stable,False +02,vision,7,0.7,0.8,-0.002132096327841282,0.0026840496248077004,16,stable,False +02,vision,8,0.8,0.9,-0.019219718873500824,0.01745792859172882,16,stable,False +02,vision,9,0.9,1.0,-0.01734579389449209,0.017423208839136055,16,stable,False +03,text,0,0.0,0.1,-0.1468745181336999,0.0319421234683677,16,stable,False +03,text,1,0.1,0.2,-0.20071300026029348,0.028826606134011213,16,stable,False +03,text,2,0.2,0.3,-0.10554380202665925,0.052538191507580274,16,stable,False +03,text,3,0.3,0.4,-0.11763328965753317,0.022682032979912947,16,stable,False +03,text,4,0.4,0.5,-0.25409176759421825,0.04328636854479889,16,stable,False +03,text,5,0.5,0.6,0.025146916043013334,0.018089850784241857,16,stable,False +03,text,6,0.6,0.7,0.37964809220284224,0.0796977127780905,16,stable,False +03,text,7,0.7,0.8,0.4260731328104157,0.08012881849553437,16,stable,False +03,text,8,0.8,0.9,0.3716314242046792,0.07532427432576366,16,stable,False +03,text,9,0.9,1.0,0.03260408714413643,0.00958417025002297,16,stable,False +03,audio,0,0.0,0.1,0.007766876835376024,0.003271961759439887,16,stable,False +03,audio,1,0.1,0.2,0.006488126469776034,0.003324157638883152,16,stable,False +03,audio,2,0.2,0.3,-0.016845647449372336,0.005399341791008501,16,stable,False +03,audio,3,0.3,0.4,0.02497231960296631,0.0029256090190421784,16,stable,False +03,audio,4,0.4,0.5,0.012833932181820273,0.0021935914267862686,16,stable,False +03,audio,5,0.5,0.6,0.004549576668068767,0.0034734011702652755,16,stable,False +03,audio,6,0.6,0.7,-0.006960852537304163,0.002996739408371313,16,stable,False +03,audio,7,0.7,0.8,0.01306554430630058,0.0035671004404954194,16,stable,False +03,audio,8,0.8,0.9,-0.005483502696733922,0.002068398928076061,16,stable,False +03,audio,9,0.9,1.0,0.024667887715622783,0.007094296879298716,16,stable,False +03,vision,0,0.0,0.1,-0.006535648368299007,0.004425495990915771,16,stable,False +03,vision,1,0.1,0.2,-0.031847413833020255,0.0160869496446553,16,stable,False +03,vision,2,0.2,0.3,-0.07149078717338853,0.02230828022286466,16,stable,False +03,vision,3,0.3,0.4,-0.07968386419815943,0.024541203945209027,16,stable,False +03,vision,4,0.4,0.5,-0.016761592589318752,0.014055884186525796,16,stable,False +03,vision,5,0.5,0.6,-0.03342015130328946,0.013764353612724005,16,stable,False +03,vision,6,0.6,0.7,-0.06931015150621533,0.023818303790187446,16,stable,False +03,vision,7,0.7,0.8,-0.014363787340698764,0.014199624226887066,16,stable,False +03,vision,8,0.8,0.9,0.05309187390957959,0.007874248770874274,16,stable,False +03,vision,9,0.9,1.0,0.026122569106519222,0.0071736234178903235,16,stable,False +04,text,0,0.0,0.1,0.9141831923043355,0.24524596536621782,16,stable,False +04,text,1,0.1,0.2,0.5140680961194448,0.1650650755456328,16,stable,False +04,text,2,0.2,0.3,-0.5401804260909557,0.09564544199803597,16,stable,False +04,text,3,0.3,0.4,0.112938666716218,0.09031893851514408,16,stable,False +04,text,4,0.4,0.5,0.6277451105415821,0.18271232691496506,16,stable,False +04,text,5,0.5,0.6,-0.514852661639452,0.11074220090820447,16,stable,False +04,text,6,0.6,0.7,-0.5971456747502089,0.09624872271555468,16,stable,False +04,text,7,0.7,0.8,0.16591994650661945,0.11185014873586584,16,stable,False +04,text,8,0.8,0.9,0.2307781662675552,0.1340995377302692,16,stable,False +04,text,9,0.9,1.0,0.6506991538917646,0.17125446269256822,16,stable,False +04,audio,0,0.0,0.1,0.02359095774590969,0.016732583123800063,16,stable,False +04,audio,1,0.1,0.2,-0.07095338852377608,0.02680729641708691,16,stable,False +04,audio,2,0.2,0.3,-0.032523399975616485,0.026842560963308136,16,stable,False +04,audio,3,0.3,0.4,0.020568767562508583,0.004943455944633252,16,stable,False +04,audio,4,0.4,0.5,-0.062041693192441016,0.02984964496093275,16,stable,False +04,audio,5,0.5,0.6,-0.038894188415724784,0.021505186204123474,16,stable,False +04,audio,6,0.6,0.7,-0.0678289420902729,0.03067686039614309,16,stable,False +04,audio,7,0.7,0.8,-0.041840177786070853,0.023952643573328304,16,stable,False +04,audio,8,0.8,0.9,-0.09816189447883517,0.03659130925672187,16,stable,False +04,audio,9,0.9,1.0,-0.023339003324508667,0.00383657670443348,16,stable,False +04,vision,0,0.0,0.1,-0.025206708174664527,0.026378259950959587,16,stable,False +04,vision,1,0.1,0.2,-0.013624444603919983,0.019716376773975103,16,stable,False +04,vision,2,0.2,0.3,-0.024314943701028824,0.005766432950559478,16,stable,False +04,vision,3,0.3,0.4,-0.05704651493579149,0.026471305143213303,16,stable,False +04,vision,4,0.4,0.5,-0.04453487694263458,0.03174308285765449,16,stable,False +04,vision,5,0.5,0.6,0.08410330553306267,0.020474813190971948,16,stable,False +04,vision,6,0.6,0.7,0.05325228441506624,0.01108765528050272,16,stable,False +04,vision,7,0.7,0.8,-0.1704186499118805,0.04873071861783258,16,stable,False +04,vision,8,0.8,0.9,-0.12445163010852411,0.04287187047836418,16,stable,False +04,vision,9,0.9,1.0,-0.13066370971500874,0.040374675786863254,16,stable,False +05,text,0,0.0,0.1,0.23293456807732582,0.044398432047631275,32,stable,False +05,text,1,0.1,0.2,0.6020670213038102,0.08588462093473578,32,stable,False +05,text,2,0.2,0.3,0.01162069255951792,0.0264620228429983,32,stable,False +05,text,3,0.3,0.4,0.041673462837934494,0.022701154674003157,32,stable,False +05,text,4,0.4,0.5,-0.24565158819314092,0.02699554567613218,32,stable,False +05,text,5,0.5,0.6,-0.03989897854626179,0.031097782791919607,32,stable,False +05,text,6,0.6,0.7,-0.2312568612396717,0.03439740072569168,32,stable,False +05,text,7,0.7,0.8,0.23074009083211422,0.050355089571634086,32,stable,False +05,text,8,0.8,0.9,-0.06878424267051741,0.02565297276253453,32,stable,False +05,text,9,0.9,1.0,0.0703399513149634,0.0362178359824638,32,stable,False +05,audio,0,0.0,0.1,-0.09578148537548259,0.0098309928222143,32,stable,False +05,audio,1,0.1,0.2,-0.00981973874149844,0.007592593964265901,32,stable,False +05,audio,2,0.2,0.3,0.05399813351687044,0.014812493209196262,32,stable,False +05,audio,3,0.3,0.4,0.08642724622040987,0.019177949831390068,32,stable,False +05,audio,4,0.4,0.5,0.026131076039746404,0.009260606751003778,32,stable,False +05,audio,5,0.5,0.6,0.07434694247785956,0.018903685651552662,32,stable,False +05,audio,6,0.6,0.7,0.0071559567004442215,0.0062492049444848475,32,stable,False +05,audio,7,0.7,0.8,0.06161860638530925,0.012505995089940969,32,stable,False +05,audio,8,0.8,0.9,0.07158913864986971,0.01793481855764846,32,stable,False +05,audio,9,0.9,1.0,0.0038970530149526894,0.009892918874126777,32,stable,False +05,vision,0,0.0,0.1,-0.014343914575874805,0.015287948382323027,32,stable,False +05,vision,1,0.1,0.2,0.1029385207220912,0.02239155389065744,32,stable,False +05,vision,2,0.2,0.3,0.13193559815408662,0.03449586787907593,32,stable,False +05,vision,3,0.3,0.4,0.08305018657119945,0.028398797419757114,32,stable,False +05,vision,4,0.4,0.5,0.1675075776875019,0.035316019190087186,32,stable,False +05,vision,5,0.5,0.6,0.06359046476427466,0.0246648711208595,32,stable,False +05,vision,6,0.6,0.7,0.020825696003157645,0.0223136347433165,32,stable,False +05,vision,7,0.7,0.8,-0.05489160324214026,0.013125629329992788,32,stable,False +05,vision,8,0.8,0.9,0.059608321462292224,0.029116810394589545,32,stable,False +05,vision,9,0.9,1.0,0.05190906283678487,0.026304850943417998,32,stable,False +06,text,0,0.0,0.1,0.18846334269619547,0.06730884201190947,64,stable,False +06,text,1,0.1,0.2,0.3957692921103444,0.07577594795535471,64,stable,False +06,text,2,0.2,0.3,0.21731206859112717,0.06105360304872178,64,stable,False +06,text,3,0.3,0.4,0.4803523705340922,0.08313842672325379,64,stable,False +06,text,4,0.4,0.5,0.2823158281680662,0.06717924423897143,64,stable,False +06,text,5,0.5,0.6,0.24262981908395886,0.035196081320967264,64,stable,False +06,text,6,0.6,0.7,0.5434295730374288,0.09424949768326749,64,stable,False +06,text,7,0.7,0.8,-0.0884781887580175,0.045925553845011666,64,stable,False +06,text,8,0.8,0.9,-0.07236569913220592,0.042965322659466294,64,stable,False +06,text,9,0.9,1.0,-0.06985152451670729,0.03293487728170707,64,stable,False +06,audio,0,0.0,0.1,-0.05214787973091006,0.0036863432908589276,64,stable,False +06,audio,1,0.1,0.2,-0.007022940117167309,0.0029470715911910565,64,stable,False +06,audio,2,0.2,0.3,0.030832908902084455,0.0060431408682659745,64,stable,False +06,audio,3,0.3,0.4,-0.0023749730025883764,0.0035272925520899194,64,stable,False +06,audio,4,0.4,0.5,0.016031424805987626,0.004621540220354766,64,stable,False +06,audio,5,0.5,0.6,0.08691148844081908,0.009988336362468354,64,stable,False +06,audio,6,0.6,0.7,-0.0011681779578793794,0.0029408348640113913,64,stable,False +06,audio,7,0.7,0.8,0.04691894384450279,0.007146872018158698,64,stable,False +06,audio,8,0.8,0.9,-0.06488956298562698,0.006090834440525607,64,stable,False +06,audio,9,0.9,1.0,0.028134224092354998,0.005504207740849057,64,stable,False +06,vision,0,0.0,0.1,0.00015291321324184537,0.006396887328841082,64,stable,False +06,vision,1,0.1,0.2,-0.00969747846829705,0.00890091467134087,64,stable,False +06,vision,2,0.2,0.3,-0.027639445528620854,0.008574950208810841,64,stable,False +06,vision,3,0.3,0.4,-0.031400189152918756,0.010176875602015844,64,stable,False +06,vision,4,0.4,0.5,-0.023442256380803883,0.00794074657376766,64,stable,False +06,vision,5,0.5,0.6,-0.007102909963577986,0.006265396068488513,64,stable,False +06,vision,6,0.6,0.7,-0.0197655062074773,0.007618730929945213,64,stable,False +06,vision,7,0.7,0.8,-0.03513202635804191,0.00942967169768761,64,stable,False +06,vision,8,0.8,0.9,-0.11203585282783024,0.016250395019693643,64,stable,False +06,vision,9,0.9,1.0,-0.02825069660320878,0.009925887578503517,64,stable,False +07,text,0,0.0,0.1,-0.2807314048986882,0.07199795779186322,16,stable,False +07,text,1,0.1,0.2,-0.09571871161460876,0.03664843045032331,16,stable,False +07,text,2,0.2,0.3,-0.13468044064939022,0.020678854689247187,16,stable,False +07,text,3,0.3,0.4,0.22829848306719214,0.07451636374234474,16,stable,False +07,text,4,0.4,0.5,0.6596076075220481,0.17048749390447948,16,stable,False +07,text,5,0.5,0.6,0.3983154036104679,0.08918721333228016,16,stable,False +07,text,6,0.6,0.7,0.10073965415358543,0.04440199355530042,16,stable,False +07,text,7,0.7,0.8,-0.12250468949787319,0.047021136776418,16,stable,False +07,text,8,0.8,0.9,0.03219079377595335,0.058013483781366754,16,stable,False +07,text,9,0.9,1.0,0.12445983663201332,0.03178747045053555,16,stable,False +07,audio,0,0.0,0.1,0.026075719855725765,0.013401243452956714,16,stable,False +07,audio,1,0.1,0.2,0.027768587926402688,0.015812423563727133,16,stable,False +07,audio,2,0.2,0.3,0.030620926059782505,0.004426927026903294,16,stable,False +07,audio,3,0.3,0.4,0.03348503413144499,0.013051232010705604,16,stable,False +07,audio,4,0.4,0.5,0.07670619210693985,0.023307670673857236,16,stable,False +07,audio,5,0.5,0.6,0.022687664488330483,0.014040747590711115,16,stable,False +07,audio,6,0.6,0.7,-0.00988765712827444,0.004206218124756885,16,stable,False +07,audio,7,0.7,0.8,0.0007308972999453545,0.006877662469259831,16,stable,False +07,audio,8,0.8,0.9,0.03251865284983069,0.01768278860743525,16,stable,False +07,audio,9,0.9,1.0,-0.003563420264981687,0.008202134202086153,16,stable,False +07,vision,0,0.0,0.1,-0.0024851257912814617,0.000789153633491801,16,stable,False +07,vision,1,0.1,0.2,0.0007033385336399078,0.007718101656332319,16,stable,False +07,vision,2,0.2,0.3,0.04117334447801113,0.010553154090053005,16,stable,False +07,vision,3,0.3,0.4,0.027776760049164295,0.007851572454693596,16,stable,False +07,vision,4,0.4,0.5,0.020356823690235615,0.008475799972083118,16,stable,False +07,vision,5,0.5,0.6,0.02346079656854272,0.008896903677679282,16,stable,False +07,vision,6,0.6,0.7,-0.005256508942693472,0.0029639681909905766,16,stable,False +07,vision,7,0.7,0.8,0.025500426650978625,0.009935565536515522,16,stable,False +07,vision,8,0.8,0.9,-0.01366146607324481,0.005163228255806352,16,stable,False +07,vision,9,0.9,1.0,0.027030720375478268,0.010367075626397076,16,stable,False +08,text,0,0.0,0.1,0.17770834435941651,0.05086179014937832,32,stable,False +08,text,1,0.1,0.2,0.2499188704532571,0.04833664988674012,32,stable,False +08,text,2,0.2,0.3,0.026259816775564104,0.07045408004852691,32,stable,False +08,text,3,0.3,0.4,0.029634585371240973,0.051879886859682006,32,stable,False +08,text,4,0.4,0.5,-0.0343631996656768,0.04545107010431922,32,stable,False +08,text,5,0.5,0.6,-0.16044770478038117,0.023143879327351045,32,stable,False +08,text,6,0.6,0.7,0.32741222821641713,0.08759215548279477,32,stable,False +08,text,7,0.7,0.8,0.3970816290238872,0.09063187172760538,32,stable,False +08,text,8,0.8,0.9,0.4221285214298405,0.09525691508464883,32,stable,False +08,text,9,0.9,1.0,0.04282134445384145,0.03360955767376876,32,stable,False +08,audio,0,0.0,0.1,0.05316140741342679,0.010873419555925998,32,stable,False +08,audio,1,0.1,0.2,0.0871599295642227,0.01438313462474187,32,stable,False +08,audio,2,0.2,0.3,0.03795783658279106,0.009266816219941269,32,stable,False +08,audio,3,0.3,0.4,-0.014795155206229538,0.0037579314828385443,32,stable,False +08,audio,4,0.4,0.5,-0.05113702290691435,0.007596154718520131,32,stable,False +08,audio,5,0.5,0.6,0.020376423664856702,0.006369232285698349,32,stable,False +08,audio,6,0.6,0.7,0.0728136120014824,0.014421761209324037,32,stable,False +08,audio,7,0.7,0.8,0.02373598760459572,0.008822048706576926,32,stable,False +08,audio,8,0.8,0.9,-0.06333806028123945,0.0073744831773825455,32,stable,False +08,audio,9,0.9,1.0,-0.01905539585277438,0.0038848974439001736,32,stable,False +08,vision,0,0.0,0.1,-0.009772224439075217,0.002849140495600707,32,stable,False +08,vision,1,0.1,0.2,-0.008020571316592395,0.0025751063934248753,32,stable,False +08,vision,2,0.2,0.3,0.0039413339691236615,0.001381266016106183,32,stable,False +08,vision,3,0.3,0.4,-0.0007593688205815852,0.0022579792590337626,32,stable,False +08,vision,4,0.4,0.5,-0.01496548531576991,0.0035957861125950168,32,stable,False +08,vision,5,0.5,0.6,0.008766540500801057,0.0015528403463582147,32,stable,False +08,vision,6,0.6,0.7,0.005108536744955927,0.0014318259607011613,32,stable,False +08,vision,7,0.7,0.8,5.5977609008550644e-05,0.0019346942286685511,32,stable,False +08,vision,8,0.8,0.9,-0.007509635470341891,0.0029836460446540864,32,stable,False +08,vision,9,0.9,1.0,-0.0030505531176459044,0.00142592014577895,32,stable,False +09,text,0,0.0,0.1,0.10768596082925797,0.07193388358513367,64,max_permutations,False +09,text,1,0.1,0.2,0.15403015854826663,0.08336394126897574,64,max_permutations,False +09,text,2,0.2,0.3,0.18160415906459093,0.08997383499850248,64,max_permutations,False +09,text,3,0.3,0.4,0.9826138447388075,0.16587439742202253,64,max_permutations,False +09,text,4,0.4,0.5,0.6115690304577583,0.13047027452942908,64,max_permutations,False +09,text,5,0.5,0.6,0.49902549386024475,0.12256492458760214,64,max_permutations,False +09,text,6,0.6,0.7,0.41781722227460705,0.12378921411803138,64,max_permutations,False +09,text,7,0.7,0.8,0.399600631557405,0.10196689811575514,64,max_permutations,False +09,text,8,0.8,0.9,0.417607262119418,0.10928116768965355,64,max_permutations,False +09,text,9,0.9,1.0,0.524572035545134,0.14065626478549254,64,max_permutations,False +09,audio,0,0.0,0.1,0.023669981528655626,0.0051520133308288595,64,max_permutations,False +09,audio,1,0.1,0.2,-0.00939754476712551,0.005473113660761007,64,max_permutations,False +09,audio,2,0.2,0.3,0.040614094366901554,0.0041078750316379965,64,max_permutations,False +09,audio,3,0.3,0.4,-0.016439422048279084,0.0072136628696657665,64,max_permutations,False +09,audio,4,0.4,0.5,-0.05226275355380494,0.0064904620363677385,64,max_permutations,False +09,audio,5,0.5,0.6,-0.10833994395215996,0.013296084753314452,64,max_permutations,False +09,audio,6,0.6,0.7,-0.03989495833229739,0.007320245875938111,64,max_permutations,False +09,audio,7,0.7,0.8,-0.013566266730776988,0.005035313936052559,64,max_permutations,False +09,audio,8,0.8,0.9,-0.038148356121382676,0.00886424122318373,64,max_permutations,False +09,audio,9,0.9,1.0,0.00043955372530035675,0.006197565930567541,64,max_permutations,False +09,vision,0,0.0,0.1,-0.08023086354660336,0.008839803929316286,64,max_permutations,False +09,vision,1,0.1,0.2,-0.07771580701228231,0.0070556649751515365,64,max_permutations,False +09,vision,2,0.2,0.3,-0.08755727780226152,0.009575022261140967,64,max_permutations,False +09,vision,3,0.3,0.4,0.0292091522278497,0.003217859298158789,64,max_permutations,False +09,vision,4,0.4,0.5,0.03450135386083275,0.005715388020666905,64,max_permutations,False +09,vision,5,0.5,0.6,0.01119528801064007,0.0025980599041537816,64,max_permutations,False +09,vision,6,0.6,0.7,0.028356000926578417,0.0036435989460922116,64,max_permutations,False +09,vision,7,0.7,0.8,0.015950414250255562,0.002734138120982871,64,max_permutations,False +09,vision,8,0.8,0.9,0.017621606966713443,0.0036707584059330954,64,max_permutations,False +09,vision,9,0.9,1.0,0.07323238368553575,0.006916269456994557,64,max_permutations,False +10,text,0,0.0,0.1,0.4140143576951232,0.1003773425374924,64,stable,False +10,text,1,0.1,0.2,0.01121065801999066,0.05246440018063112,64,stable,False +10,text,2,0.2,0.3,-0.2394247827178333,0.04743588983292265,64,stable,False +10,text,3,0.3,0.4,0.17991569339937996,0.06999216901021174,64,stable,False +10,text,4,0.4,0.5,0.1494577877165284,0.06218177702920901,64,stable,False +10,text,5,0.5,0.6,0.19738677851273678,0.0656933121703012,64,stable,False +10,text,6,0.6,0.7,-0.033821260469267145,0.042924653056262786,64,stable,False +10,text,7,0.7,0.8,0.255947959041805,0.0672016446508023,64,stable,False +10,text,8,0.8,0.9,0.9977771317498991,0.1185689206389685,64,stable,False +10,text,9,0.9,1.0,0.9122313311236212,0.12249904316796838,64,stable,False +10,audio,0,0.0,0.1,-0.02243388789065648,0.00799590230531354,64,stable,False +10,audio,1,0.1,0.2,-0.02562665325240232,0.009263428262468369,64,stable,False +10,audio,2,0.2,0.3,-0.06718353304313496,0.012705761263230067,64,stable,False +10,audio,3,0.3,0.4,-0.044055100399418734,0.010404476609097359,64,stable,False +10,audio,4,0.4,0.5,-0.055598239006940275,0.010983050320342466,64,stable,False +10,audio,5,0.5,0.6,-0.026588419001200236,0.009006937078574168,64,stable,False +10,audio,6,0.6,0.7,-0.048232322980766185,0.0092315476739546,64,stable,False +10,audio,7,0.7,0.8,-0.032605959902866744,0.009424441098727502,64,stable,False +10,audio,8,0.8,0.9,0.0025217785441782326,0.00500671756094839,64,stable,False +10,audio,9,0.9,1.0,0.015890972776105627,0.006464776977388028,64,stable,False +10,vision,0,0.0,0.1,-0.045404995515127666,0.015533622691539765,64,stable,False +10,vision,1,0.1,0.2,-0.12280285042652395,0.02296312934995374,64,stable,False +10,vision,2,0.2,0.3,-0.10574701335281134,0.01811528331885136,64,stable,False +10,vision,3,0.3,0.4,-0.07697499348432757,0.015453804387098997,64,stable,False +10,vision,4,0.4,0.5,-0.020605886122211814,0.010291220091879721,64,stable,False +10,vision,5,0.5,0.6,0.024123090362991206,0.010118095100470485,64,stable,False +10,vision,6,0.6,0.7,-0.036647429878939874,0.01316007433152269,64,stable,False +10,vision,7,0.7,0.8,-0.002815269588609226,0.011069457121929277,64,stable,False +10,vision,8,0.8,0.9,-0.07251107609772589,0.019379081696173132,64,stable,False +10,vision,9,0.9,1.0,-0.08244680045754649,0.01965900097928553,64,stable,False +11,text,0,0.0,0.1,0.08317508539767005,0.04954351005429095,32,stable,False +11,text,1,0.1,0.2,1.0367089239880443,0.12217696010638715,32,stable,False +11,text,2,0.2,0.3,1.2976987939327955,0.11042302703400987,32,stable,False +11,text,3,0.3,0.4,0.5735141683544498,0.09506295243377091,32,stable,False +11,text,4,0.4,0.5,0.15150513211847283,0.0625122025037525,32,stable,False +11,text,5,0.5,0.6,-0.38421271872357465,0.050680664789031195,32,stable,False +11,text,6,0.6,0.7,-0.3501955643296242,0.04217346507123575,32,stable,False +11,text,7,0.7,0.8,-0.47991615513456054,0.052959887051836574,32,stable,False +11,text,8,0.8,0.9,-0.5079016497475095,0.058471655584300994,32,stable,False +11,text,9,0.9,1.0,0.15129060903564095,0.05744181582104048,32,stable,False +11,audio,0,0.0,0.1,0.0018712930323090404,0.0016871066184651411,32,stable,False +11,audio,1,0.1,0.2,-0.011189746350282803,0.0028909078455731205,32,stable,False +11,audio,2,0.2,0.3,0.00937529353541322,0.001603648872778038,32,stable,False +11,audio,3,0.3,0.4,0.0382854119525291,0.004117772505279889,32,stable,False +11,audio,4,0.4,0.5,-0.0017727971717249602,0.0013478594362271007,32,stable,False +11,audio,5,0.5,0.6,-0.014202558100805618,0.0027746389203791243,32,stable,False +11,audio,6,0.6,0.7,0.014589822400012054,0.0017130217499706257,32,stable,False +11,audio,7,0.7,0.8,-0.015179806243395433,0.0032442644218416243,32,stable,False +11,audio,8,0.8,0.9,-0.012747056491207331,0.0019680061212582643,32,stable,False +11,audio,9,0.9,1.0,-0.022822956962045282,0.002532858065194674,32,stable,False +11,vision,0,0.0,0.1,-0.01954834582284093,0.012090989810776621,32,stable,False +11,vision,1,0.1,0.2,0.05016999944928102,0.004326543193533188,32,stable,False +11,vision,2,0.2,0.3,0.04453502534306608,0.007363136352267293,32,stable,False +11,vision,3,0.3,0.4,0.01773451207554899,0.011142940924327957,32,stable,False +11,vision,4,0.4,0.5,0.017307151400018483,0.00985831921864745,32,stable,False +11,vision,5,0.5,0.6,-0.02267471485538408,0.010323882270265166,32,stable,False +11,vision,6,0.6,0.7,-0.10345343902008608,0.01803664928996661,32,stable,False +11,vision,7,0.7,0.8,-0.14296824307530187,0.02796628354308197,32,stable,False +11,vision,8,0.8,0.9,-0.026814361510332674,0.011924244492282523,32,stable,False +11,vision,9,0.9,1.0,-0.0681090060970746,0.0207660319901182,32,stable,False +12,text,0,0.0,0.1,-0.22706957020272966,0.028270720163018168,64,max_permutations,False +12,text,1,0.1,0.2,-0.05126071081031114,0.045658680137213685,64,max_permutations,False +12,text,2,0.2,0.3,-0.0007502666339860298,0.028853364095135444,64,max_permutations,False +12,text,3,0.3,0.4,0.21081679909548257,0.05735916403069648,64,max_permutations,False +12,text,4,0.4,0.5,0.12954307746258564,0.05510274057041151,64,max_permutations,False +12,text,5,0.5,0.6,0.4112846369534964,0.07828389201504268,64,max_permutations,False +12,text,6,0.6,0.7,0.21491040698310826,0.0586021692647383,64,max_permutations,False +12,text,7,0.7,0.8,0.5635053327641799,0.08192870595866984,64,max_permutations,False +12,text,8,0.8,0.9,0.27470678662939463,0.054513203557270434,64,max_permutations,False +12,text,9,0.9,1.0,0.5560293968446786,0.08999529164226666,64,max_permutations,False +12,audio,0,0.0,0.1,-0.006170861408463679,0.004798780931243528,64,max_permutations,False +12,audio,1,0.1,0.2,-0.015362115103926044,0.00258995441478962,64,max_permutations,False +12,audio,2,0.2,0.3,-0.0036864284047624096,0.0025797287784515488,64,max_permutations,False +12,audio,3,0.3,0.4,-0.015235978120472282,0.003646172476064035,64,max_permutations,False +12,audio,4,0.4,0.5,-0.02139794803224504,0.0035675354515246264,64,max_permutations,False +12,audio,5,0.5,0.6,-0.02214156695845304,0.0035430130764409293,64,max_permutations,False +12,audio,6,0.6,0.7,-0.0007115534390322864,0.002588959114002684,64,max_permutations,False +12,audio,7,0.7,0.8,0.0035326022407389246,0.0027588227666979635,64,max_permutations,False +12,audio,8,0.8,0.9,-0.011062974917877,0.00326390416772475,64,max_permutations,False +12,audio,9,0.9,1.0,-0.0037636279957951047,0.00324061542754634,64,max_permutations,False +12,vision,0,0.0,0.1,-0.006337987069855444,0.002301637977363389,64,max_permutations,False +12,vision,1,0.1,0.2,-0.003579808588256128,0.002670007175739108,64,max_permutations,False +12,vision,2,0.2,0.3,-0.0029591818747576326,0.0024554396975754325,64,max_permutations,False +12,vision,3,0.3,0.4,-0.004654540549381636,0.0025672597662441243,64,max_permutations,False +12,vision,4,0.4,0.5,-0.007126071686798241,0.0032008051787007353,64,max_permutations,False +12,vision,5,0.5,0.6,0.0013174179548514076,0.0021634175654936597,64,max_permutations,False +12,vision,6,0.6,0.7,-0.0006302003675955348,0.0021550750661950445,64,max_permutations,False +12,vision,7,0.7,0.8,0.0035120519023621455,0.0019089263253645567,64,max_permutations,False +12,vision,8,0.8,0.9,-0.0024549047666369006,0.0023895079936194896,64,max_permutations,False +12,vision,9,0.9,1.0,-0.00935584181570448,0.0025952001050152935,64,max_permutations,False +13,text,0,0.0,0.1,-0.17346966860350221,0.04551824983440317,16,stable,False +13,text,1,0.1,0.2,-0.1855074695777148,0.048209407087956735,16,stable,False +13,text,2,0.2,0.3,-0.01550071535166353,0.02172503173230116,16,stable,False +13,text,3,0.3,0.4,0.03290311177261174,0.023499213226645957,16,stable,False +13,text,4,0.4,0.5,0.1900026180082932,0.03912008599103367,16,stable,False +13,text,5,0.5,0.6,-0.13953795097768307,0.03286516961115438,16,stable,False +13,text,6,0.6,0.7,-0.12420026247855276,0.034847916907905535,16,stable,False +13,text,7,0.7,0.8,0.06725766509771347,0.013366956065681956,16,stable,False +13,text,8,0.8,0.9,0.019064208026975393,0.009466481989791187,16,stable,False +13,text,9,0.9,1.0,-0.013832075521349907,0.016580771920967817,16,stable,False +13,audio,0,0.0,0.1,-0.003313009685371071,0.0026748742871660547,16,stable,False +13,audio,1,0.1,0.2,0.009257545345462859,0.00395976823501722,16,stable,False +13,audio,2,0.2,0.3,0.019524238829035312,0.009409215219406412,16,stable,False +13,audio,3,0.3,0.4,0.04462461557704955,0.009484220323037713,16,stable,False +13,audio,4,0.4,0.5,0.014484378276392817,0.006951426960012512,16,stable,False +13,audio,5,0.5,0.6,0.012509375344961882,0.0042440774833653536,16,stable,False +13,audio,6,0.6,0.7,-0.005791692950879224,0.0033683296649581777,16,stable,False +13,audio,7,0.7,0.8,-0.06496133211476263,0.010975644658470165,16,stable,False +13,audio,8,0.8,0.9,0.024504587054252625,0.008614278400996093,16,stable,False +13,audio,9,0.9,1.0,-0.0003851763322018087,0.002261146242311167,16,stable,False +13,vision,0,0.0,0.1,0.027977536898106337,0.01829329870391982,16,stable,False +13,vision,1,0.1,0.2,0.027950285119004548,0.01830403216449929,16,stable,False +13,vision,2,0.2,0.3,0.04380461911205202,0.01990456380465491,16,stable,False +13,vision,3,0.3,0.4,0.04016937082633376,0.019893152391324876,16,stable,False +13,vision,4,0.4,0.5,0.02692084072623402,0.014545380772931507,16,stable,False +13,vision,5,0.5,0.6,0.018954191356897354,0.0034149259512102565,16,stable,False +13,vision,6,0.6,0.7,0.06265713414177299,0.022916999451708442,16,stable,False +13,vision,7,0.7,0.8,0.035298386588692665,0.01428720167143491,16,stable,False +13,vision,8,0.8,0.9,0.016107458621263504,0.0028639631718960505,16,stable,False +13,vision,9,0.9,1.0,0.0221631471067667,0.015163890241149372,16,stable,False +14,text,0,0.0,0.1,-0.24021617890684865,0.033546336635416424,64,stable,False +14,text,1,0.1,0.2,-0.16829417634289712,0.031083983220011557,64,stable,False +14,text,2,0.2,0.3,-0.026778001891216263,0.016457377836681397,64,stable,False +14,text,3,0.3,0.4,-0.23496718815295026,0.03471429644640515,64,stable,False +14,text,4,0.4,0.5,-0.07602742733433843,0.018601321194474392,64,stable,False +14,text,5,0.5,0.6,-0.1848787011404056,0.027805338574264888,64,stable,False +14,text,6,0.6,0.7,-0.11567743131308816,0.026433178143646617,64,stable,False +14,text,7,0.7,0.8,0.010257720685331151,0.01339085884044731,64,stable,False +14,text,8,0.8,0.9,-0.02071622767834924,0.016884469675709564,64,stable,False +14,text,9,0.9,1.0,0.19881457716110162,0.016145402060817323,64,stable,False +14,audio,0,0.0,0.1,0.04010411523631774,0.01146093680602742,64,stable,False +14,audio,1,0.1,0.2,0.04478503065183759,0.00739354475285859,64,stable,False +14,audio,2,0.2,0.3,0.0689044322934933,0.014591065681536624,64,stable,False +14,audio,3,0.3,0.4,0.01389613194623962,0.008440384415967835,64,stable,False +14,audio,4,0.4,0.5,0.050327645847573876,0.008836046028886341,64,stable,False +14,audio,5,0.5,0.6,0.06914294773014262,0.012219951146377578,64,stable,False +14,audio,6,0.6,0.7,-0.0026600561977829784,0.006211330361771348,64,stable,False +14,audio,7,0.7,0.8,0.031035248859552667,0.01052433811992653,64,stable,False +14,audio,8,0.8,0.9,0.04291828433633782,0.010814406550777271,64,stable,False +14,audio,9,0.9,1.0,-0.005668461497407407,0.00619452354852307,64,stable,False +14,vision,0,0.0,0.1,0.15063073497731239,0.030452212115541423,64,stable,False +14,vision,1,0.1,0.2,0.12773362622829154,0.025200581382778576,64,stable,False +14,vision,2,0.2,0.3,0.12687427486525849,0.025112801421750597,64,stable,False +14,vision,3,0.3,0.4,-0.005829234694829211,0.014181377501399051,64,stable,False +14,vision,4,0.4,0.5,0.00405814714031294,0.013759351953527931,64,stable,False +14,vision,5,0.5,0.6,-0.0036393722402863204,0.01355151981097642,64,stable,False +14,vision,6,0.6,0.7,0.08243993236101232,0.022398971495241983,64,stable,False +14,vision,7,0.7,0.8,0.10188267307239585,0.019902515686870136,64,stable,False +14,vision,8,0.8,0.9,-0.01266075111925602,0.010491981541790036,64,stable,False +14,vision,9,0.9,1.0,0.10029858519556001,0.021908087719144022,64,stable,False +15,text,0,0.0,0.1,-0.10142144392011687,0.042694541186588914,64,stable,False +15,text,1,0.1,0.2,0.09707848849939182,0.055215124041656585,64,stable,False +15,text,2,0.2,0.3,0.13559447845909745,0.06324687955850496,64,stable,False +15,text,3,0.3,0.4,0.11068305122898892,0.053299640584360736,64,stable,False +15,text,4,0.4,0.5,0.2955384205270093,0.07968247454766018,64,stable,False +15,text,5,0.5,0.6,0.2989993432711344,0.07534712658869172,64,stable,False +15,text,6,0.6,0.7,0.35361291031586006,0.07374311830841751,64,stable,False +15,text,7,0.7,0.8,0.6307718228781596,0.10564715215974335,64,stable,False +15,text,8,0.8,0.9,0.5166861609613989,0.07958889354707821,64,stable,False +15,text,9,0.9,1.0,0.14607270518899895,0.05596183481799439,64,stable,False +15,audio,0,0.0,0.1,0.023986150801647455,0.010435384871041542,64,stable,False +15,audio,1,0.1,0.2,0.013128890102962032,0.00828707965116855,64,stable,False +15,audio,2,0.2,0.3,0.019199409725842997,0.009546299116270906,64,stable,False +15,audio,3,0.3,0.4,0.02484546389314346,0.011000575266127782,64,stable,False +15,audio,4,0.4,0.5,0.08063976746052504,0.013485841107433012,64,stable,False +15,audio,5,0.5,0.6,0.031274444540031254,0.010320627288276802,64,stable,False +15,audio,6,0.6,0.7,0.012011096550850198,0.009406050233467344,64,stable,False +15,audio,7,0.7,0.8,0.051887454581446946,0.012573701216354476,64,stable,False +15,audio,8,0.8,0.9,0.04616080122650601,0.0102020937327251,64,stable,False +15,audio,9,0.9,1.0,0.07411868256167509,0.015540107592829568,64,stable,False +15,vision,0,0.0,0.1,-0.04629640790517442,0.0057401169280415386,64,stable,False +15,vision,1,0.1,0.2,0.018162530177505687,0.008625161146161646,64,stable,False +15,vision,2,0.2,0.3,0.0050220011617057025,0.006319620294920681,64,stable,False +15,vision,3,0.3,0.4,0.035088738484773785,0.008608333873168842,64,stable,False +15,vision,4,0.4,0.5,-0.07519077090546489,0.005561055077117622,64,stable,False +15,vision,5,0.5,0.6,0.1403894612158183,0.014683791668457322,64,stable,False +15,vision,6,0.6,0.7,0.03396641701692715,0.008402957882278083,64,stable,False +15,vision,7,0.7,0.8,0.11469262198079377,0.015664318080706844,64,stable,False +15,vision,8,0.8,0.9,0.004600239131832495,0.004925049542946882,64,stable,False +15,vision,9,0.9,1.0,0.008389886701479554,0.006321938824445505,64,stable,False +16,text,0,0.0,0.1,0.27613481408479856,0.11225502909395489,64,max_permutations,False +16,text,1,0.1,0.2,0.2249024673437816,0.10404059449465208,64,max_permutations,False +16,text,2,0.2,0.3,0.07591621148458216,0.06490398135463397,64,max_permutations,False +16,text,3,0.3,0.4,0.921995702527056,0.17974664239520405,64,max_permutations,False +16,text,4,0.4,0.5,0.6722130109192221,0.14760563796106158,64,max_permutations,False +16,text,5,0.5,0.6,0.5154972045202157,0.13389670208260918,64,max_permutations,False +16,text,6,0.6,0.7,0.47328798567468766,0.12307288579620322,64,max_permutations,False +16,text,7,0.7,0.8,0.46695831519173225,0.09927178278303314,64,max_permutations,False +16,text,8,0.8,0.9,0.6816840192259406,0.15785233957273828,64,max_permutations,False +16,text,9,0.9,1.0,-0.05372784006613074,0.02808113341356427,64,max_permutations,False +16,audio,0,0.0,0.1,-0.009475557359110098,0.001969012500903294,64,max_permutations,False +16,audio,1,0.1,0.2,-0.009745011469931342,0.0023627873202600558,64,max_permutations,False +16,audio,2,0.2,0.3,-0.022983237795415334,0.0026346801931409628,64,max_permutations,False +16,audio,3,0.3,0.4,-0.010865375144931022,0.0019948697206365242,64,max_permutations,False +16,audio,4,0.4,0.5,0.02456347769475542,0.0033065870154926286,64,max_permutations,False +16,audio,5,0.5,0.6,0.022985375915595796,0.0035950666435201274,64,max_permutations,False +16,audio,6,0.6,0.7,-0.04458897296717623,0.004860509598899562,64,max_permutations,False +16,audio,7,0.7,0.8,0.00020007837156299502,0.002176781281365927,64,max_permutations,False +16,audio,8,0.8,0.9,0.04933613885077648,0.004938507907049691,64,max_permutations,False +16,audio,9,0.9,1.0,0.02085994657682022,0.001980010909479503,64,max_permutations,False +16,vision,0,0.0,0.1,-0.004623666805855464,0.0022184895653467526,64,max_permutations,False +16,vision,1,0.1,0.2,-0.010318904263840523,0.0033496799205144485,64,max_permutations,False +16,vision,2,0.2,0.3,-0.008324586597154848,0.0036025348004011153,64,max_permutations,False +16,vision,3,0.3,0.4,-0.023601458029588684,0.004546559749863349,64,max_permutations,False +16,vision,4,0.4,0.5,0.00097469131287653,0.002398848577724479,64,max_permutations,False +16,vision,5,0.5,0.6,0.025678786776552442,0.0017144960949332388,64,max_permutations,False +16,vision,6,0.6,0.7,-0.0034266270376974717,0.00312468035284321,64,max_permutations,False +16,vision,7,0.7,0.8,-0.001737202168442309,0.0031106273859997343,64,max_permutations,False +16,vision,8,0.8,0.9,0.0021646023451467045,0.002949454278468333,64,max_permutations,False +16,vision,9,0.9,1.0,-0.02201819232868729,0.004685276293953575,64,max_permutations,False +17,text,0,0.0,0.1,0.3213029764010571,0.10350884941151615,64,max_permutations,False +17,text,1,0.1,0.2,0.5778402565338183,0.12594419923546785,64,max_permutations,False +17,text,2,0.2,0.3,0.2155533851182554,0.06659076185461864,64,max_permutations,False +17,text,3,0.3,0.4,0.5425265368248802,0.11329573263481668,64,max_permutations,False +17,text,4,0.4,0.5,0.2964746331854258,0.07527183376118962,64,max_permutations,False +17,text,5,0.5,0.6,0.2951276770909317,0.08701247977824281,64,max_permutations,False +17,text,6,0.6,0.7,0.2056336021341849,0.07828681394570655,64,max_permutations,False +17,text,7,0.7,0.8,0.35881203078315593,0.09112697012310823,64,max_permutations,False +17,text,8,0.8,0.9,0.3491082674881909,0.0936541357904053,64,max_permutations,False +17,text,9,0.9,1.0,0.0877597646904178,0.0664043926943639,64,max_permutations,False +17,audio,0,0.0,0.1,0.024635682202642784,0.003284924917776687,64,max_permutations,False +17,audio,1,0.1,0.2,-0.01588039187481627,0.004688207551997496,64,max_permutations,False +17,audio,2,0.2,0.3,-0.018846274149836972,0.005297152212336929,64,max_permutations,False +17,audio,3,0.3,0.4,-0.004117848118767142,0.0038689927282576707,64,max_permutations,False +17,audio,4,0.4,0.5,-0.010488038533367217,0.004344037277658568,64,max_permutations,False +17,audio,5,0.5,0.6,-0.028853188967332244,0.006528010879442298,64,max_permutations,False +17,audio,6,0.6,0.7,-0.012923963600769639,0.005138186081148229,64,max_permutations,False +17,audio,7,0.7,0.8,-0.04658543673576787,0.007233190366284945,64,max_permutations,False +17,audio,8,0.8,0.9,-0.05781004441087134,0.008355095087043485,64,max_permutations,False +17,audio,9,0.9,1.0,-0.0047242903092410415,0.0050342070421285695,64,max_permutations,False +17,vision,0,0.0,0.1,0.09849374537589028,0.02258639372309501,64,max_permutations,False +17,vision,1,0.1,0.2,0.025029940763488412,0.010643142758195797,64,max_permutations,False +17,vision,2,0.2,0.3,0.0031036475265864283,0.008721470149376975,64,max_permutations,False +17,vision,3,0.3,0.4,0.02950779438833706,0.01318911818329894,64,max_permutations,False +17,vision,4,0.4,0.5,0.12606115915696137,0.0222139038534662,64,max_permutations,False +17,vision,5,0.5,0.6,0.0826382775849197,0.016717598732732784,64,max_permutations,False +17,vision,6,0.6,0.7,0.10496403771685436,0.018235298625612517,64,max_permutations,False +17,vision,7,0.7,0.8,-0.0014881068200338632,0.011092231436624694,64,max_permutations,False +17,vision,8,0.8,0.9,0.056959778448799625,0.01771320116241097,64,max_permutations,False +17,vision,9,0.9,1.0,-0.03017688638647087,0.00978345210859298,64,max_permutations,False +18,text,0,0.0,0.1,0.44462686391489115,0.06349549411546251,32,stable,False +18,text,1,0.1,0.2,0.4641269795683911,0.06469902574993466,32,stable,False +18,text,2,0.2,0.3,0.26492878315912094,0.0360747787061787,32,stable,False +18,text,3,0.3,0.4,0.14193018546211533,0.030561183888971465,32,stable,False +18,text,4,0.4,0.5,0.00016883714124560356,0.034286925257080124,32,stable,False +18,text,5,0.5,0.6,-0.5257265120017109,0.06523310800276245,32,stable,False +18,text,6,0.6,0.7,-0.23096102502313443,0.03588915259490077,32,stable,False +18,text,7,0.7,0.8,-0.44532709631312173,0.04771371609079108,32,stable,False +18,text,8,0.8,0.9,-0.215724701891304,0.043152340892017714,32,stable,False +18,text,9,0.9,1.0,0.21881412724906113,0.0412351797268357,32,stable,False +18,audio,0,0.0,0.1,0.0026914111513178796,0.0012307270695554396,32,stable,False +18,audio,1,0.1,0.2,-0.02207712992094457,0.0035637936420267295,32,stable,False +18,audio,2,0.2,0.3,0.0004370304523035884,0.0013554621330095845,32,stable,False +18,audio,3,0.3,0.4,-0.029923082562163472,0.0032689031037291664,32,stable,False +18,audio,4,0.4,0.5,-0.010571632999926805,0.001739130517737338,32,stable,False +18,audio,5,0.5,0.6,0.009696273831650615,0.001215274885415808,32,stable,False +18,audio,6,0.6,0.7,-0.005392109640524723,0.001348878868917852,32,stable,False +18,audio,7,0.7,0.8,0.0027699833444785327,0.0007018617979253065,32,stable,False +18,audio,8,0.8,0.9,0.02641373744700104,0.004531255529369288,32,stable,False +18,audio,9,0.9,1.0,0.007069897837936878,0.0030215639316384145,32,stable,False +18,vision,0,0.0,0.1,0.009719536974444054,0.006060012388177373,32,stable,False +18,vision,1,0.1,0.2,0.023099806101527065,0.008916198455485566,32,stable,False +18,vision,2,0.2,0.3,0.01991078184801154,0.00799192143507949,32,stable,False +18,vision,3,0.3,0.4,0.044334021033137105,0.011979889252874971,32,stable,False +18,vision,4,0.4,0.5,-0.002103865146636963,0.0007614726994460468,32,stable,False +18,vision,5,0.5,0.6,-0.0002940924750873819,0.005549861273718779,32,stable,False +18,vision,6,0.6,0.7,-0.01154317194595933,0.0025653596162566817,32,stable,False +18,vision,7,0.7,0.8,0.020218590609147213,0.007385840909165094,32,stable,False +18,vision,8,0.8,0.9,0.009409133344888687,0.004353700031287672,32,stable,False +18,vision,9,0.9,1.0,0.03824285670998506,0.011000746181690882,32,stable,False +19,text,0,0.0,0.1,0.39794124715263024,0.04562997959934864,64,max_permutations,False +19,text,1,0.1,0.2,-0.40687543744570576,0.06507680687466239,64,max_permutations,False +19,text,2,0.2,0.3,-0.5426625471009174,0.05613958719441816,64,max_permutations,False +19,text,3,0.3,0.4,-0.4369588294503046,0.04800534471757422,64,max_permutations,False +19,text,4,0.4,0.5,-0.46953652174852323,0.05119107154545671,64,max_permutations,False +19,text,5,0.5,0.6,-0.015359458047896624,0.02170880657292385,64,max_permutations,False +19,text,6,0.6,0.7,-0.09237738396041095,0.02674835922665995,64,max_permutations,False +19,text,7,0.7,0.8,-0.39915040116466116,0.05917197424804057,64,max_permutations,False +19,text,8,0.8,0.9,0.9807364776206668,0.0906903936170207,64,max_permutations,False +19,text,9,0.9,1.0,0.2861173703568056,0.040364097308103225,64,max_permutations,False +19,audio,0,0.0,0.1,0.007687557925237343,0.002573989704062053,64,max_permutations,False +19,audio,1,0.1,0.2,0.004003813926829025,0.0038638864800786435,64,max_permutations,False +19,audio,2,0.2,0.3,-0.06272895631263964,0.005990930294959438,64,max_permutations,False +19,audio,3,0.3,0.4,-0.045318261269130744,0.005418861510306392,64,max_permutations,False +19,audio,4,0.4,0.5,0.05776199467072729,0.005838694624668898,64,max_permutations,False +19,audio,5,0.5,0.6,0.031531810280284844,0.004485617856069247,64,max_permutations,False +19,audio,6,0.6,0.7,-0.036698280993732624,0.003963007146807431,64,max_permutations,False +19,audio,7,0.7,0.8,-0.04412255165516399,0.0054655529697979904,64,max_permutations,False +19,audio,8,0.8,0.9,0.0729582949570613,0.00742283266450262,64,max_permutations,False +19,audio,9,0.9,1.0,0.03199312731157988,0.004110076744642543,64,max_permutations,False +19,vision,0,0.0,0.1,0.02713141730055213,0.013875462638664378,64,max_permutations,False +19,vision,1,0.1,0.2,0.11611285155231599,0.026976130554260222,64,max_permutations,False +19,vision,2,0.2,0.3,0.07810062216594815,0.02202074069598431,64,max_permutations,False +19,vision,3,0.3,0.4,0.038468335478683,0.017430974099180496,64,max_permutations,False +19,vision,4,0.4,0.5,0.057399069890379906,0.018638118736303586,64,max_permutations,False +19,vision,5,0.5,0.6,0.14544087984540965,0.028862239876774477,64,max_permutations,False +19,vision,6,0.6,0.7,0.12720882409485057,0.027767235259820368,64,max_permutations,False +19,vision,7,0.7,0.8,0.03505652994499542,0.015656104459435268,64,max_permutations,False +19,vision,8,0.8,0.9,0.09244590152229648,0.026334513618838293,64,max_permutations,False +19,vision,9,0.9,1.0,0.08089280045533087,0.025923112974604284,64,max_permutations,False +20,text,0,0.0,0.1,0.47541837859898806,0.08084614976373775,64,stable,False +20,text,1,0.1,0.2,0.2830378959479276,0.05851389646581134,64,stable,False +20,text,2,0.2,0.3,0.06894207181176171,0.03646051152387709,64,stable,False +20,text,3,0.3,0.4,0.14732616345281713,0.04195753949413667,64,stable,False +20,text,4,0.4,0.5,0.2211901356058661,0.04497266623043044,64,stable,False +20,text,5,0.5,0.6,0.17311706964392215,0.051432049073633516,64,stable,False +20,text,6,0.6,0.7,-0.1659611079376191,0.02116889347908944,64,stable,False +20,text,7,0.7,0.8,-0.12126856748363934,0.027266546104199377,64,stable,False +20,text,8,0.8,0.9,0.356910382892238,0.06372137677059009,64,stable,False +20,text,9,0.9,1.0,0.2721480450127274,0.055633627242429165,64,stable,False +20,audio,0,0.0,0.1,0.06879722249868792,0.009241933730653996,64,stable,False +20,audio,1,0.1,0.2,0.0734821704973001,0.009381487300125198,64,stable,False +20,audio,2,0.2,0.3,-0.010988269874360412,0.003113222443417005,64,stable,False +20,audio,3,0.3,0.4,0.04577479236468207,0.007238996449918193,64,stable,False +20,audio,4,0.4,0.5,0.0004804949276149273,0.003520295612097436,64,stable,False +20,audio,5,0.5,0.6,0.004908705101115629,0.0050695838457634436,64,stable,False +20,audio,6,0.6,0.7,-0.029079530053422786,0.0038778563254469197,64,stable,False +20,audio,7,0.7,0.8,0.014496768184471875,0.005370674964341348,64,stable,False +20,audio,8,0.8,0.9,-0.05241296952590346,0.004016791912509551,64,stable,False +20,audio,9,0.9,1.0,0.008779666779446416,0.004979878265916824,64,stable,False +20,vision,0,0.0,0.1,-0.06146337787504308,0.01204398644334877,64,stable,False +20,vision,1,0.1,0.2,0.19105098325235303,0.020379792999463592,64,stable,False +20,vision,2,0.2,0.3,-0.16483094592695124,0.01830270558188342,64,stable,False +20,vision,3,0.3,0.4,-0.12310345962760039,0.012765805609259074,64,stable,False +20,vision,4,0.4,0.5,-0.10652847339224536,0.011805191251376312,64,stable,False +20,vision,5,0.5,0.6,-0.08434701265650801,0.014922654396071152,64,stable,False +20,vision,6,0.6,0.7,-0.03815167227003258,0.01158295437313063,64,stable,False +20,vision,7,0.7,0.8,-0.07440588585450314,0.011802421548538522,64,stable,False +20,vision,8,0.8,0.9,0.25895043746277224,0.023195527211723146,64,stable,False +20,vision,9,0.9,1.0,0.09462569202878512,0.010956266291883248,64,stable,False diff --git a/submit/final/output/q3/ati_ho/attachment4_predictions.csv b/submit/final/output/q3/ati_ho/attachment4_predictions.csv new file mode 100644 index 0000000..96eb5f5 --- /dev/null +++ b/submit/final/output/q3/ati_ho/attachment4_predictions.csv @@ -0,0 +1,21 @@ +case_id,predicted_class,predicted_intensity,prob_negative,prob_neutral,prob_positive,conditional_negative_magnitude,conditional_positive_magnitude,coordinate_mode,physical_time_alignment,text_observed_steps,audio_observed_steps,vision_observed_steps,true_label_available +01,neutral,0.0,0.09335917234420776,0.492781400680542,0.41385942697525024,0.7683022022247314,0.7918088436126709,relative_progress,False,50,50,50,False +02,positive,1.0754910707473755,0.16660422086715698,0.2815896272659302,0.5518062114715576,1.0387691259384155,1.0754910707473755,relative_progress,False,50,50,50,False +03,negative,-1.1482162475585938,0.44677555561065674,0.35495230555534363,0.19827206432819366,1.1482162475585938,1.054591417312622,relative_progress,False,50,50,50,False +04,negative,-1.1313621997833252,0.6154006123542786,0.08184759318828583,0.3027518689632416,1.1313621997833252,1.2142971754074097,relative_progress,False,50,50,50,False +05,positive,0.9983435869216919,0.019440362229943275,0.1781618446111679,0.8023977875709534,0.8999792337417603,0.9983435869216919,relative_progress,False,50,50,50,False +06,positive,1.2635153532028198,0.04338030517101288,0.12275035679340363,0.8338693380355835,1.089220643043518,1.2635153532028198,relative_progress,False,50,50,50,False +07,positive,0.7555097937583923,0.024313107132911682,0.20876865088939667,0.7669181823730469,0.748427152633667,0.7555097937583923,relative_progress,False,50,50,50,False +08,positive,0.906437873840332,0.018894990906119347,0.16367757320404053,0.8174274563789368,0.8192697763442993,0.906437873840332,relative_progress,False,50,50,50,False +09,negative,-1.5102474689483643,0.9667553901672363,0.016179252415895462,0.01706531085073948,1.5102474689483643,1.1577949523925781,relative_progress,False,50,50,50,False +10,negative,-1.4183083772659302,0.845405101776123,0.03885689750313759,0.11573806405067444,1.4183083772659302,1.3615806102752686,relative_progress,False,50,50,50,False +11,negative,-1.3695042133331299,0.6915677785873413,0.11874549835920334,0.18968670070171356,1.3695042133331299,1.220651626586914,relative_progress,False,50,50,50,False +12,negative,-1.1265901327133179,0.8298471570014954,0.11778073757886887,0.05237210541963577,1.1265901327133179,0.9112517237663269,relative_progress,False,50,50,50,False +13,positive,0.8690536022186279,0.05612684041261673,0.46271824836730957,0.4811549782752991,1.0537734031677246,0.8690536022186279,relative_progress,False,50,50,48,False +14,positive,0.9438773393630981,0.052272193133831024,0.43238261342048645,0.5153452157974243,0.8826612234115601,0.9438773393630981,relative_progress,False,50,50,50,False +15,positive,1.1347935199737549,0.00835143681615591,0.04237542673945427,0.9492731094360352,0.6205173134803772,1.1347935199737549,relative_progress,False,50,50,50,False +16,negative,-1.5337121486663818,0.9742470383644104,0.014193418435752392,0.01155958790332079,1.5337121486663818,1.0518016815185547,relative_progress,False,50,50,47,False +17,positive,1.2830696105957031,0.00731800589710474,0.02694552205502987,0.965736448764801,0.8673832416534424,1.2830696105957031,relative_progress,False,50,50,50,False +18,neutral,0.0,0.36853352189064026,0.4732035994529724,0.15826284885406494,1.0774030685424805,0.9822005033493042,relative_progress,False,50,50,50,False +19,positive,1.3935961723327637,0.4146651029586792,0.11420381814241409,0.4711310565471649,1.244720220565796,1.3935961723327637,relative_progress,False,50,50,50,False +20,positive,1.0786213874816895,0.018150269985198975,0.14706102013587952,0.8347886800765991,0.8807858228683472,1.0786213874816895,relative_progress,False,50,50,50,False diff --git a/submit/final/q2/__init__.py b/submit/final/q2/__init__.py new file mode 100644 index 0000000..23139b5 --- /dev/null +++ b/submit/final/q2/__init__.py @@ -0,0 +1 @@ +"""Q2 training, comparison, and inference pipelines.""" diff --git a/submit/final/q2/deep_learning/__init__.py b/submit/final/q2/deep_learning/__init__.py new file mode 100644 index 0000000..d2b231f --- /dev/null +++ b/submit/final/q2/deep_learning/__init__.py @@ -0,0 +1 @@ +"""Maintained Q2 deep-learning schemes.""" diff --git a/submit/final/q2/deep_learning/q2/__init__.py b/submit/final/q2/deep_learning/q2/__init__.py new file mode 100644 index 0000000..8214e10 --- /dev/null +++ b/submit/final/q2/deep_learning/q2/__init__.py @@ -0,0 +1 @@ +"""Q2 multimodal emotion-recognition experiments.""" diff --git a/submit/final/q2/deep_learning/q2/data.py b/submit/final/q2/deep_learning/q2/data.py new file mode 100644 index 0000000..59bff02 --- /dev/null +++ b/submit/final/q2/deep_learning/q2/data.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +import pickle +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import numpy as np + +from ....data_paths import ATTACHMENT2, PROJECT_ROOT + + +ROOT = PROJECT_ROOT +MODALITIES = ("text", "audio", "vision") + + +@dataclass +class Split: + x: tuple[np.ndarray, np.ndarray, np.ndarray] + mask: np.ndarray # N x T x 3 + y_cls: np.ndarray + y_reg: np.ndarray + ids: list[str] + + @property + def n(self) -> int: + return len(self.y_cls) + + @property + def steps(self) -> int: + return int(self.x[0].shape[1]) + + +@dataclass +class RobustStats: + center: tuple[np.ndarray, np.ndarray, np.ndarray] + scale: tuple[np.ndarray, np.ndarray, np.ndarray] + + def save(self, path: Path) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + np.savez_compressed( + path, + text_center=self.center[0], text_scale=self.scale[0], + audio_center=self.center[1], audio_scale=self.scale[1], + vision_center=self.center[2], vision_scale=self.scale[2], + ) + + @classmethod + def load(cls, path: Path) -> "RobustStats": + with np.load(path) as data: + return cls( + tuple(data[f"{m}_center"].astype(np.float32) for m in MODALITIES), + tuple(data[f"{m}_scale"].astype(np.float32) for m in MODALITIES), + ) + + +def _unpickle(path: Path) -> dict[str, Any]: + with path.open("rb") as stream: + return pickle.load(stream, encoding="latin1") + + +def _ids_and_targets(part: dict[str, Any]) -> tuple[list[str], np.ndarray, np.ndarray]: + ids = [str(x) for x in part["id"]] + y_cls = np.asarray(part["classification_labels"], dtype=np.int64).reshape(-1) + y_reg = np.asarray(part["regression_labels"], dtype=np.float32).reshape(-1) + return ids, y_cls, y_reg + + +def _text_mask(part: dict[str, Any]) -> np.ndarray: + tokens = np.asarray(part["text_bert"]) + if tokens.ndim != 3 or tokens.shape[1] < 2: + raise ValueError(f"unexpected text_bert shape: {tokens.shape}") + # MOSEI text_bert rows are input_ids, input_mask, segment_ids. + return tokens[:, 1, :].astype(bool) + + +def load_aligned(path: Path | None = None) -> dict[str, Split]: + path = path or ATTACHMENT2 / "aligned_50.pkl" + raw = _unpickle(path) + result: dict[str, Split] = {} + for name in ("train", "valid"): + part = raw[name] + xs = tuple(np.asarray(part[m], dtype=np.float32) for m in MODALITIES) + masks = [ + _text_mask(part), + np.any(np.isfinite(xs[1]) & (xs[1] != 0), axis=-1), + np.any(np.isfinite(xs[2]) & (xs[2] != 0), axis=-1), + ] + mask = np.stack(masks, axis=-1) + ids, y_cls, y_reg = _ids_and_targets(part) + if any(x.shape[1] != 50 for x in xs): + raise ValueError(f"{name} aligned feature tensors must have 50 slots") + result[name] = Split(xs, mask, y_cls, y_reg, ids) + train_videos = {x.split("$_$", 1)[0] for x in result["train"].ids} + valid_videos = {x.split("$_$", 1)[0] for x in result["valid"].ids} + overlap = train_videos & valid_videos + if overlap: + raise ValueError(f"official train/valid split leaks {len(overlap)} source video ids") + return result + + +def _resample_rows_to_50(values: np.ndarray, lengths: list[int] | np.ndarray) -> tuple[np.ndarray, np.ndarray]: + n, source_steps, dim = values.shape + output = np.zeros((n, 50, dim), dtype=np.float32) + mask = np.zeros((n, 50), dtype=bool) + lengths_arr = np.asarray(lengths, dtype=np.int64).reshape(-1) + for i in range(n): + length = int(np.clip(lengths_arr[i], 0, source_steps)) + if length == 0: + continue + source = np.nan_to_num(values[i, :length], nan=0.0, posinf=0.0, neginf=0.0) + observed = np.any(source != 0, axis=-1) + for j in range(50): + left = int(np.floor(j * length / 50)) + right = max(left + 1, int(np.ceil((j + 1) * length / 50))) + right = min(right, length) + use = observed[left:right] + if use.any(): + output[i, j] = source[left:right][use].mean(axis=0) + mask[i, j] = True + return output, mask + + +def load_fixed_window(path: Path | None = None) -> dict[str, Split]: + """Build a matched 50-slot equal-window control from the unaligned file.""" + path = path or ATTACHMENT2 / "unaligned_50.pkl" + raw = _unpickle(path) + result: dict[str, Split] = {} + for name in ("train", "valid"): + part = raw[name] + text = np.asarray(part["text"], dtype=np.float32) + audio, audio_mask = _resample_rows_to_50(part["audio"], part["audio_lengths"]) + vision, vision_mask = _resample_rows_to_50(part["vision"], part["vision_lengths"]) + text_mask = _text_mask(part) + xs = (text, audio, vision) + mask = np.stack((text_mask, audio_mask, vision_mask), axis=-1) + ids, y_cls, y_reg = _ids_and_targets(part) + result[name] = Split(xs, mask, y_cls, y_reg, ids) + return result + + +def fit_robust_stats(split: Split) -> RobustStats: + centers: list[np.ndarray] = [] + scales: list[np.ndarray] = [] + for modality in range(3): + observed = split.mask[:, :, modality].reshape(-1) + values = split.x[modality].reshape(-1, split.x[modality].shape[-1])[observed] + if not len(values): + raise ValueError(f"no observed values for {MODALITIES[modality]}") + values = np.nan_to_num(values, nan=0.0, posinf=0.0, neginf=0.0) + center = np.median(values, axis=0) + mad = np.median(np.abs(values - center), axis=0) + scale = 1.4826 * mad + std = np.std(values, axis=0) + scale = np.where(scale > 1e-6, scale, std) + scale = np.where(scale > 1e-6, scale, 1.0) + centers.append(center.astype(np.float32)) + scales.append(scale.astype(np.float32)) + return RobustStats(tuple(centers), tuple(scales)) + + +def apply_robust_stats(split: Split, stats: RobustStats) -> Split: + xs: list[np.ndarray] = [] + for modality in range(3): + values = (split.x[modality] - stats.center[modality]) / stats.scale[modality] + values = np.nan_to_num(values, nan=0.0, posinf=0.0, neginf=0.0) + values *= split.mask[:, :, modality, None] + xs.append(values.astype(np.float32, copy=False)) + return Split(tuple(xs), split.mask.copy(), split.y_cls, split.y_reg, split.ids) + + +def corrupt_masks( + base: np.ndarray, + ratio: float, + modalities: tuple[int, ...], + seed: int, +) -> np.ndarray: + result = base.copy() + rng = np.random.default_rng(seed) + n, steps, _ = result.shape + width = max(1, min(steps, int(round(ratio * steps)))) + starts = rng.integers(0, steps - width + 1, size=n) + for row, start in enumerate(starts.tolist()): + result[row, start:start + width, list(modalities)] = False + return result + + +def augment_masks(base: np.ndarray, rng: np.random.Generator) -> np.ndarray: + result = base.copy() + n, steps, _ = result.shape + for row in range(n): + if rng.random() >= 0.85: + continue + count = int(rng.integers(1, 4)) + modalities = rng.choice(3, size=count, replace=False) + ratio = float(rng.choice((0.10, 0.20, 0.30))) + width = max(1, int(round(ratio * steps))) + start = int(rng.integers(0, steps - width + 1)) + result[row, start:start + width, modalities] = False + return result + + +def shift_audio_vision(split: Split, seed: int, max_shift: int = 10) -> Split: + rng = np.random.default_rng(seed) + xs = [x.copy() for x in split.x] + masks = split.mask.copy() + for row in range(split.n): + for modality in (1, 2): + shift = int(rng.integers(1, max_shift + 1)) + if rng.random() < 0.5: + shift = -shift + xs[modality][row] = np.roll(xs[modality][row], shift, axis=0) + masks[row, :, modality] = np.roll(masks[row, :, modality], shift) + return Split(tuple(xs), masks, split.y_cls, split.y_reg, split.ids) diff --git a/submit/final/q2/deep_learning/q2/evaluate_math_protocol.py b/submit/final/q2/deep_learning/q2/evaluate_math_protocol.py new file mode 100644 index 0000000..751b359 --- /dev/null +++ b/submit/final/q2/deep_learning/q2/evaluate_math_protocol.py @@ -0,0 +1,614 @@ +"""Score the frozen EarlyConcat and MoFE checkpoints using the math-Q2 protocol. + +This script performs no training and selects no models. It evaluates the saved +three-seed checkpoints on the official labeled test split once, and reuses the +fixed 42-scenario validation-mask audit as the controlled-missingness protocol. +This source is retained for protocol helpers used by the standalone training runner. +""" +from __future__ import annotations + +import csv +import argparse +import hashlib +import json +import math +import statistics +import time +from collections import defaultdict +from pathlib import Path +from typing import Any + +import numpy as np +import torch +from sklearn.metrics import accuracy_score, f1_score, mean_absolute_error, mean_squared_error +from torch import nn + +from .data import ( + ATTACHMENT2, + MODALITIES, + RobustStats, + Split, + _ids_and_targets, + _text_mask, + _unpickle, + apply_robust_stats, + fit_robust_stats, + load_aligned, +) +from .models import AlignedFusionModel +from .mofe import MixtureOfFusionExperts +from .train_mofe import MODEL_CONFIG, _predict, _device_for, EARLYCONCAT, MOFE7_MLP + + +Q2_ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = Q2_ROOT.parents[1] +REFERENCE_DIR = Q2_ROOT / "outputs" / "followups" / "R01_selected_model_reevaluation" +OUTPUT_DIR = Q2_ROOT / "outputs" / "followups" / "R02_math_protocol_evaluation" +SEEDS = (42, 3407, 2026) +BOOTSTRAP_REPS = 1000 +TEST_BOOTSTRAP_SEED = 20260925 +AURC_BOOTSTRAP_SEED = 20260926 +SCENARIO_SEED = 20261833 +METHODS = (EARLYCONCAT, MOFE7_MLP) +CURVE_MODES = ("single", "sync", "partial", "async") +CURVE_RATES = (0.0, 0.1, 0.3, 0.5, 0.7) + + +def write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + if not rows: + return + path.parent.mkdir(parents=True, exist_ok=True) + fields = list(dict.fromkeys(key for row in rows for key in row)) + with path.open("w", newline="", encoding="utf-8-sig") as stream: + writer = csv.DictWriter(stream, fieldnames=fields) + writer.writeheader() + writer.writerows(rows) + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def split_from_part(part: dict[str, Any]) -> Split: + xs = tuple(np.asarray(part[name], dtype=np.float32) for name in MODALITIES) + masks = [ + _text_mask(part), + np.any(np.isfinite(xs[1]) & (xs[1] != 0), axis=-1), + np.any(np.isfinite(xs[2]) & (xs[2] != 0), axis=-1), + ] + ids, y_cls, y_reg = _ids_and_targets(part) + return Split(xs, np.stack(masks, axis=-1), y_cls, y_reg, ids) + + +def load_splits(feature_path: Path) -> dict[str, Split]: + raw = _unpickle(feature_path) + usual = load_aligned(feature_path) + splits = {"train": usual["train"], "valid": usual["valid"], "test": split_from_part(raw["test"])} + groups = { + name: {sample_id.split("$_$", 1)[0] for sample_id in split.ids} + for name, split in splits.items() + } + for first, second in (("train", "valid"), ("train", "test"), ("valid", "test")): + overlap = groups[first] & groups[second] + if overlap: + raise ValueError(f"official {first}/{second} source-video groups overlap: {len(overlap)}") + for name, split in splits.items(): + expected = np.where(split.y_reg < 0, 0, np.where(split.y_reg == 0, 1, 2)) + if not np.array_equal(expected, split.y_cls): + raise ValueError(f"{name}: classification labels disagree with strict sign of regression labels") + return splits + + +def metrics(split: Split, logits: np.ndarray, intensity: np.ndarray, indices: np.ndarray | None = None) -> dict[str, float]: + if indices is None: + indices = np.arange(split.n) + y_cls = split.y_cls[indices] + y_reg = split.y_reg[indices] + pred_cls = np.asarray(logits)[indices].argmax(axis=-1) + pred_reg = np.clip(np.asarray(intensity).reshape(-1)[indices], -3.0, 3.0) + return { + "accuracy": float(accuracy_score(y_cls, pred_cls)), + "macro_f1": float(f1_score(y_cls, pred_cls, labels=[0, 1, 2], average="macro", zero_division=0)), + "mae": float(mean_absolute_error(y_reg, pred_reg)), + "rmse": float(math.sqrt(mean_squared_error(y_reg, pred_reg))), + "pearson": float(np.corrcoef(y_reg, pred_reg)[0, 1]) if np.std(y_reg) > 0 and np.std(pred_reg) > 0 else float("nan"), + } + + +def load_model(method: str, seed: int, dims: tuple[int, int, int], device: torch.device) -> nn.Module: + if method == EARLYCONCAT: + checkpoint = REFERENCE_DIR / "models" / "baselines" / "concat" / f"seed_{seed}" / "model_best.pt" + model: nn.Module = AlignedFusionModel("concat", dims=dims).to(device) + state = torch.load(checkpoint, map_location=device, weights_only=False) + if state.get("kind") != "concat" or int(state.get("seed", -1)) != seed: + raise ValueError(f"unexpected EarlyConcat checkpoint: {checkpoint}") + elif method == MOFE7_MLP: + checkpoint = REFERENCE_DIR / "models" / MOFE7_MLP / f"seed_{seed}" / "model_best.pt" + model = MixtureOfFusionExperts(dims=dims, **MODEL_CONFIG).to(device) + state = torch.load(checkpoint, map_location=device, weights_only=False) + if state.get("config") != MODEL_CONFIG or int(state.get("seed", -1)) != seed: + raise ValueError(f"unexpected MoFE checkpoint: {checkpoint}") + else: + raise ValueError(f"unknown model {method}") + if tuple(state.get("dims", ())) != dims: + raise ValueError(f"feature dimensions do not match checkpoint: {checkpoint}") + model.load_state_dict(state["state_dict"]) + model.eval() + return model + + +def best_interval(visible: np.ndarray, wanted: int, cap: int, location: str, rng: np.random.Generator) -> tuple[int, int] | None: + steps = len(visible) + candidates: list[tuple[int, int, int, int]] = [] + for left in range(steps): + hits = 0 + for right in range(left, steps): + hits += int(visible[right]) + count = min(hits, cap) + if count: + candidates.append((abs(count - wanted), right - left + 1, left, right)) + if not candidates: + return None + best = min((error, span) for error, span, _, _ in candidates) + tied = [(left, right) for error, span, left, right in candidates if (error, span) == best] + if location == "start": + return min(tied, key=lambda pair: (pair[0], pair[1])) + if location == "end": + return max(tied, key=lambda pair: (pair[1], pair[0])) + if location == "middle": + center = (steps - 1) / 2 + return min(tied, key=lambda pair: (abs((pair[0] + pair[1]) / 2 - center), pair[0])) + if location != "random": + raise ValueError(f"unknown interval location: {location}") + return tied[int(rng.integers(0, len(tied)))] + + +def spread_short_spans(visible: np.ndarray, wanted: int, cap: int) -> np.ndarray: + positions = np.flatnonzero(visible) + count = min(int(wanted), int(cap), len(positions)) + chosen = np.zeros(len(visible), dtype=bool) + if count <= 0: + return chosen + n_spans = min(3, count) + chunks = np.array_split(positions, n_spans) + allocations = [count // n_spans + int(i < count % n_spans) for i in range(n_spans)] + for chunk, amount in zip(chunks, allocations): + if amount <= 0 or len(chunk) == 0: + continue + amount = min(amount, len(chunk)) + start = max(0, (len(chunk) - amount) // 2) + chosen[chunk[start:start + amount]] = True + return chosen + + +def continuous_mask( + original: np.ndarray, + rate: float, + mode: str, + rng: np.random.Generator, + *, + modalities: tuple[int, ...] | None = None, + location: str = "random", + span_structure: str = "long", +) -> np.ndarray: + """Reproduce math/Q2 continuous masking on this model's observed positions.""" + observed = np.asarray(original, dtype=bool) + result = observed.copy() + if rate <= 0 or mode == "none": + return result + steps, modality_count = observed.shape + present = [m for m in range(modality_count) if observed[:, m].any()] + if not present: + return result + if modalities is not None: + selected = [int(m) for m in modalities if int(m) in present] + if not selected: + return result + elif mode == "single": + selected = [int(rng.choice(present))] + elif mode in {"sync", "partial", "async"}: + if len(present) == 1: + selected = present + else: + count = int(rng.integers(2, min(3, len(present)) + 1)) + selected = sorted(int(v) for v in rng.choice(present, size=count, replace=False)) + else: + raise ValueError(f"unknown mask mode: {mode}") + + def max_hide(modality: int) -> int: + count = int(observed[:, modality].sum()) + keep = max(1, int(math.ceil(0.2 * count))) + return max(0, count - keep) + + target = {m: min(max_hide(m), int(round(rate * int(observed[:, m].sum())))) for m in selected} + if mode == "sync": + span = max(1, int(round(rate * steps))) + if location == "start": + left = 0 + elif location == "end": + left = steps - span + elif location == "middle": + left = (steps - span) // 2 + elif location == "random": + left = int(rng.integers(0, max(1, steps - span + 1))) + else: + raise ValueError(f"unknown interval location: {location}") + right = min(steps - 1, left + span - 1) + for m in selected: + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + amount = min(len(candidates), max_hide(m), target[m]) + if amount: + offset = 0 if location != "end" else len(candidates) - amount + result[candidates[max(0, offset):max(0, offset) + amount], m] = False + else: + common_span = max(1, int(round(rate * steps))) + for rank, m in enumerate(selected): + wanted = target[m] + if wanted <= 0: + continue + cap = max_hide(m) + if span_structure == "multi_short": + hide = spread_short_spans(observed[:, m], wanted, cap) + elif span_structure != "long": + raise ValueError(f"unknown span structure: {span_structure}") + elif mode == "single" and location != "random": + # Place a contiguous block at the requested relative location + # among observed positions, while keeping the selected-source + # missing amount fixed. This avoids treating padding as time. + interval = best_interval(observed[:, m], wanted, cap, location, rng) + hide = np.zeros(steps, dtype=bool) + if interval is not None: + left, right = interval + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + amount = min(len(candidates), wanted, cap) + if amount: + offset = 0 if location != "end" else len(candidates) - amount + hide[candidates[max(0, offset):max(0, offset) + amount]] = True + elif mode in {"partial", "async"}: + if mode == "partial": + base_left = int(rng.integers(0, max(1, steps - common_span + 1))) if location == "random" else ( + 0 if location == "start" else steps - common_span if location == "end" else (steps - common_span) // 2 + ) + offset = int(round(rank * common_span * 0.5)) + else: + base_left = 0 if location == "random" else ( + 0 if location == "start" else steps - common_span if location == "end" else (steps - common_span) // 2 + ) + available = max(1, steps - common_span + 1) + offsets = np.rint(np.linspace(0, max(0, available - 1), len(selected))).astype(int) + if location == "random": + rng.shuffle(offsets) + offset = int(offsets[rank]) + left = min(max(0, base_left + offset), max(0, steps - common_span)) + right = min(steps - 1, left + common_span - 1) + hide = np.zeros(steps, dtype=bool) + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + amount = min(len(candidates), wanted, cap) + if amount: + hide[candidates[:amount]] = True + else: + interval = best_interval(observed[:, m], wanted, cap, location, rng) + hide = np.zeros(steps, dtype=bool) + if interval is not None: + left, right = interval + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + amount = min(len(candidates), wanted, cap) + if amount: + offset = 0 if location != "end" else len(candidates) - amount + hide[candidates[max(0, offset):max(0, offset) + amount]] = True + result[hide, m] = False + return result + + +def scenario_seed(seed: int, sample_id: str, key: str) -> int: + return int.from_bytes(hashlib.sha256(f"{seed}:{sample_id}:{key}".encode()).digest()[:8], "little") + + +def make_scenarios(valid: Split, seed: int = SCENARIO_SEED) -> dict[str, np.ndarray]: + scenarios = {"0.0/none": valid.mask.copy()} + for rate in CURVE_RATES[1:]: + for mode in CURVE_MODES: + key = f"{rate:.1f}/{mode}" + scenarios[key] = np.stack([ + continuous_mask(mask, rate, mode, np.random.default_rng(scenario_seed(seed, sample_id, key))) + for sample_id, mask in zip(valid.ids, valid.mask) + ]) + modality_sets = (((0,), "T"), ((1,), "A"), ((2,), "V"), ((0, 1), "TA"), ((0, 2), "TV"), ((1, 2), "AV"), ((0, 1, 2), "TAV")) + for selected, label in modality_sets: + key = f"0.3/modality_{label}" + scenarios[key] = np.stack([ + continuous_mask(mask, 0.3, "sync", np.random.default_rng(scenario_seed(seed, sample_id, key)), modalities=selected) + for sample_id, mask in zip(valid.ids, valid.mask) + ]) + for modality_index, label in enumerate(("T", "A", "V")): + for location in ("start", "middle", "end"): + key = f"0.3/location_{location}_{label}" + scenarios[key] = np.stack([ + continuous_mask(mask, 0.3, "single", np.random.default_rng(scenario_seed(seed, sample_id, key)), modalities=(modality_index,), location=location) + for sample_id, mask in zip(valid.ids, valid.mask) + ]) + for structure in ("long", "multi_short"): + key = f"0.3/span_{structure}_{label}" + scenarios[key] = np.stack([ + continuous_mask(mask, 0.3, "single", np.random.default_rng(scenario_seed(seed, sample_id, key)), modalities=(modality_index,), span_structure=structure) + for sample_id, mask in zip(valid.ids, valid.mask) + ]) + for mode in ("sync", "partial", "async"): + key = f"0.3/synchrony_{mode}" + scenarios[key] = np.stack([ + continuous_mask(mask, 0.3, mode, np.random.default_rng(scenario_seed(seed, sample_id, key)), modalities=(0, 1, 2)) + for sample_id, mask in zip(valid.ids, valid.mask) + ]) + return scenarios + + +def actual_additional_rates(base: np.ndarray, scenarios: dict[str, np.ndarray]) -> dict[str, np.ndarray]: + result = {} + observed = base.sum(axis=1) + for scenario, current in scenarios.items(): + newly_hidden = base & ~current + hidden_count = newly_hidden.sum(axis=1) + by_modality = np.divide( + hidden_count, + observed, + out=np.full(hidden_count.shape, np.nan, dtype=np.float64), + where=observed > 0, + ) + result[scenario] = np.nanmean(by_modality, axis=1) + return result + + +def aurc_from_curve(rates: list[float], maes: list[float]) -> float: + order = np.argsort(np.asarray(rates), kind="stable") + x = np.asarray(rates, dtype=np.float64)[order] + y = np.asarray(maes, dtype=np.float64)[order] + unique_x, inverse = np.unique(x, return_inverse=True) + unique_y = np.asarray([y[inverse == i].mean() for i in range(len(unique_x))]) + if len(unique_x) <= 1 or unique_x[-1] <= 0: + return float(maes[0]) + return float(np.trapezoid(unique_y, unique_x) / unique_x[-1]) + + +def curve_scenarios(mode: str) -> list[str]: + return ["0.0/none"] + [f"{rate:.1f}/{mode}" for rate in CURVE_RATES[1:]] + + +def group_indices(ids: list[str]) -> tuple[list[str], dict[str, np.ndarray]]: + groups = sorted({sample_id.split("$_$", 1)[0] for sample_id in ids}) + mapping = {group: np.flatnonzero(np.asarray([x.split("$_$", 1)[0] == group for x in ids])) for group in groups} + return groups, mapping + + +def bootstrap_clean_test( + split: Split, + preds: dict[tuple[str, int], dict[str, np.ndarray]], +) -> list[dict[str, Any]]: + groups, mapping = group_indices(split.ids) + rng = np.random.default_rng(TEST_BOOTSTRAP_SEED) + draws: dict[str, list[float]] = defaultdict(list) + for _ in range(BOOTSTRAP_REPS): + chosen = rng.choice(groups, size=len(groups), replace=True) + indices = np.concatenate([mapping[group] for group in chosen]) + per_method = {} + for method in METHODS: + per_seed = [metrics(split, preds[(method, seed)]["logits"], preds[(method, seed)]["intensity"], indices) for seed in SEEDS] + per_method[method] = {key: float(np.mean([row[key] for row in per_seed])) for key in per_seed[0]} + for metric in per_method[EARLYCONCAT]: + draws[metric].append(per_method[MOFE7_MLP][metric] - per_method[EARLYCONCAT][metric]) + rows = [] + for metric, values in draws.items(): + rows.append({ + "comparison": "MoFE-7 + MLP Router minus EarlyConcat + BiGRU", + "metric": metric, + "delta_mean_over_seeds": float(np.mean([r[metric] for r in [ + metrics(split, preds[(MOFE7_MLP, seed)]["logits"], preds[(MOFE7_MLP, seed)]["intensity"]) + for seed in SEEDS + ]]) - np.mean([r[metric] for r in [ + metrics(split, preds[(EARLYCONCAT, seed)]["logits"], preds[(EARLYCONCAT, seed)]["intensity"]) + for seed in SEEDS + ]])), + "bootstrap_ci_2p5": float(np.quantile(values, 0.025)), + "bootstrap_ci_97p5": float(np.quantile(values, 0.975)), + "bootstrap_probability_delta_gt_0": float(np.mean(np.asarray(values) > 0)), + "replicates": BOOTSTRAP_REPS, + "resampling_unit": "source video id", + "paired": True, + "seed": TEST_BOOTSTRAP_SEED, + }) + return rows + + +def bootstrap_aurc( + valid: Split, + predictions: dict[tuple[str, int, str], dict[str, np.ndarray]], + scenarios: dict[str, np.ndarray], + rates_by_sample: dict[str, np.ndarray], +) -> list[dict[str, Any]]: + groups, mapping = group_indices(valid.ids) + rng = np.random.default_rng(AURC_BOOTSTRAP_SEED) + delta_by_mode: dict[str, list[float]] = {mode: [] for mode in CURVE_MODES} + for _ in range(BOOTSTRAP_REPS): + chosen = rng.choice(groups, size=len(groups), replace=True) + indices = np.concatenate([mapping[group] for group in chosen]) + for mode in CURVE_MODES: + keys = curve_scenarios(mode) + model_aucs: dict[str, list[float]] = {method: [] for method in METHODS} + for method in METHODS: + for seed in SEEDS: + xs = [float(np.nanmean(rates_by_sample[key][indices])) for key in keys] + ys = [float(np.abs(valid.y_reg[indices] - predictions[(method, seed, key)]["intensity"][indices]).mean()) for key in keys] + model_aucs[method].append(aurc_from_curve(xs, ys)) + delta_by_mode[mode].append(float(np.mean(model_aucs[MOFE7_MLP]) - np.mean(model_aucs[EARLYCONCAT]))) + point = {} + for mode in CURVE_MODES: + model_aucs = {} + for method in METHODS: + model_aucs[method] = [] + for seed in SEEDS: + keys = curve_scenarios(mode) + xs = [float(np.nanmean(rates_by_sample[key])) for key in keys] + ys = [float(np.abs(valid.y_reg - predictions[(method, seed, key)]["intensity"]).mean()) for key in keys] + model_aucs[method].append(aurc_from_curve(xs, ys)) + point[mode] = float(np.mean(model_aucs[MOFE7_MLP]) - np.mean(model_aucs[EARLYCONCAT])) + rows = [] + for mode, values in delta_by_mode.items(): + rows.append({ + "mode": mode, + "delta_aurc_mae_mofe_minus_earlyconcat": point[mode], + "bootstrap_ci_2p5": float(np.quantile(values, 0.025)), + "bootstrap_ci_97p5": float(np.quantile(values, 0.975)), + "bootstrap_probability_delta_lt_0": float(np.mean(np.asarray(values) < 0)), + "replicates": BOOTSTRAP_REPS, + "resampling_unit": "source video id", + "paired": True, + "seed": AURC_BOOTSTRAP_SEED, + }) + return rows + + +def run(device_name: str = "auto", batch_size: int = 64, masks_only: bool = False) -> None: + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + device = _device_for(device_name) + torch.set_num_threads(4) + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + + feature_path = ATTACHMENT2 / "aligned_50.pkl" + with (REFERENCE_DIR / "run_manifest.json").open("r", encoding="utf-8") as stream: + reference_manifest = json.load(stream) + if sha256(feature_path) != reference_manifest["feature_sha256"]: + raise ValueError("current official feature file hash differs from the checkpoint evaluation manifest") + + raw_splits = load_splits(feature_path) + train = raw_splits["train"] + valid = raw_splits["valid"] + test = raw_splits["test"] + scaler_path = REFERENCE_DIR / "aligned_robust_stats.npz" + stats = RobustStats.load(scaler_path) + computed = fit_robust_stats(train) + scaler_diff = max( + max(float(np.max(np.abs(a - b))) for a, b in zip(computed.center, stats.center)), + max(float(np.max(np.abs(a - b))) for a, b in zip(computed.scale, stats.scale)), + ) + if scaler_diff > 1e-6: + raise ValueError(f"checkpoint scaler is not the train-only scaler (max difference {scaler_diff})") + valid = apply_robust_stats(valid, stats) + test = apply_robust_stats(test, stats) + dims = tuple(x.shape[-1] for x in train.x) + + if not masks_only: + # Final, clean official-test evaluation; no retraining or selection occurs here. + test_predictions: dict[tuple[str, int], dict[str, np.ndarray]] = {} + test_rows: list[dict[str, Any]] = [] + for method in METHODS: + for seed in SEEDS: + model = load_model(method, seed, dims, device) + prediction = _predict(model, test, test.mask, device, batch_size) + test_predictions[(method, seed)] = prediction + test_rows.append({"method": method, "seed": seed, "n_test": test.n, **metrics(test, prediction["logits"], prediction["intensity"])}) + del model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + summary_rows = [] + for method in METHODS: + subset = [row for row in test_rows if row["method"] == method] + for metric in ("accuracy", "macro_f1", "mae", "rmse", "pearson"): + values = [float(row[metric]) for row in subset] + summary_rows.append({"method": method, "metric": metric, "mean": float(np.mean(values)), "sd_across_seeds": float(np.std(values, ddof=1))}) + write_csv(OUTPUT_DIR / "official_test_metrics_by_seed.csv", test_rows) + write_csv(OUTPUT_DIR / "official_test_summary.csv", summary_rows) + write_csv(OUTPUT_DIR / "official_test_paired_bootstrap.csv", bootstrap_clean_test(test, test_predictions)) + + # Reproduce the math-Q2 42-scenario design with a per-sample stable seed, + # while applying it to the observation masks used to train these models. + scenario_masks = make_scenarios(valid) + rates_by_sample = actual_additional_rates(valid.mask, scenario_masks) + condition_predictions: dict[tuple[str, int, str], dict[str, np.ndarray]] = {} + condition_rows: list[dict[str, Any]] = [] + for method in METHODS: + for seed in SEEDS: + model = load_model(method, seed, dims, device) + for scenario, masks in scenario_masks.items(): + prediction = _predict(model, valid, masks, device, batch_size) + condition_predictions[(method, seed, scenario)] = prediction + values = metrics(valid, prediction["logits"], prediction["intensity"]) + condition_rows.append({ + "method": method, + "seed": seed, + "scenario": scenario, + "realized_additional_global_rate": float(np.nanmean(rates_by_sample[scenario])), + "n_valid": valid.n, + **values, + }) + del model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + write_csv(OUTPUT_DIR / "controlled_metrics_by_scenario.csv", condition_rows) + + auc_rows: list[dict[str, Any]] = [] + for method in METHODS: + for seed in SEEDS: + for mode in CURVE_MODES: + keys = curve_scenarios(mode) + xs = [float(np.nanmean(rates_by_sample[key])) for key in keys] + ys = [float(np.abs(valid.y_reg - condition_predictions[(method, seed, key)]["intensity"]).mean()) for key in keys] + auc_rows.append({"method": method, "seed": seed, "mask_mode": mode, "aurc_mae": aurc_from_curve(xs, ys), "rates_realized": json.dumps(xs)}) + write_csv(OUTPUT_DIR / "aurc_mae_by_mode_seed.csv", auc_rows) + auc_summary = [] + for method in METHODS: + for mode in CURVE_MODES: + values = [row["aurc_mae"] for row in auc_rows if row["method"] == method and row["mask_mode"] == mode] + auc_summary.append({"method": method, "mask_mode": mode, "mean": float(np.mean(values)), "sd_across_seeds": float(np.std(values, ddof=1))}) + write_csv(OUTPUT_DIR / "aurc_mae_summary.csv", auc_summary) + write_csv(OUTPUT_DIR / "aurc_mae_paired_bootstrap.csv", bootstrap_aurc(valid, condition_predictions, scenario_masks, rates_by_sample)) + + manifest = { + "experiment": "Frozen EarlyConcat vs MoFE-7 evaluation under math/Q2 test protocol", + "created_unix": time.time(), + "device": str(device), + "cuda_device": torch.cuda.get_device_name(0) if device.type == "cuda" else None, + "feature_file": str(feature_path), + "feature_sha256": sha256(feature_path), + "representation": "official aligned_50 ordered positions; not physical-time bins", + "train_valid_test_counts": {name: split.n for name, split in raw_splits.items()}, + "source_video_groups": {name: len({sample_id.split("$_$", 1)[0] for sample_id in split.ids}) for name, split in raw_splits.items()}, + "official_group_splits_disjoint": True, + "test_evaluation": ( + "one final clean evaluation on official labeled test split; no training/model selection/calibration" + if not masks_only else "test outputs preserved from the earlier single evaluation; no test prediction was rerun" + ), + "test_prediction_performed_this_invocation": not masks_only, + "seeds": list(SEEDS), + "checkpoint_source": str(REFERENCE_DIR / "models"), + "train_only_scaler": str(scaler_path), + "scaler_max_abs_difference_from_train_refit": scaler_diff, + "test_labels_used_for_training_or_selection": False, + "controlled_missingness": { + "scenario_seed": SCENARIO_SEED, + "scenario_design": "math/Q2 42-scenario design regenerated on the Q2 models' BERT attention-mask base", + "scenarios": len(scenario_masks), + "AURC": "normalized trapezoidal area of MAE over realized equal-modality-weighted added missing rate, at 0/.1/.3/.5/.7 for single/sync/partial/async", + }, + "bootstrap": { + "replicates": BOOTSTRAP_REPS, + "test_seed": TEST_BOOTSTRAP_SEED, + "aurc_seed": AURC_BOOTSTRAP_SEED, + "unit": "source video id", + "paired": True, + }, + } + (OUTPUT_DIR / "run_manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") + print(f"wrote math-protocol comparison to {OUTPUT_DIR}") + print(f"n_test={test.n}; n_valid={valid.n}; device={device}; scenarios={len(scenario_masks)}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--masks-only", action="store_true", help="Recompute validation mask scenarios without rerunning official-test inference") + args = parser.parse_args() + run(masks_only=args.masks_only) diff --git a/submit/final/q2/deep_learning/q2/models.py b/submit/final/q2/deep_learning/q2/models.py new file mode 100644 index 0000000..4ab3d11 --- /dev/null +++ b/submit/final/q2/deep_learning/q2/models.py @@ -0,0 +1,4 @@ +"""Compatibility import for the maintained model registry.""" +from ....model.early_concat import AlignedFusionModel + +__all__ = ["AlignedFusionModel"] diff --git a/submit/final/q2/deep_learning/q2/mofe.py b/submit/final/q2/deep_learning/q2/mofe.py new file mode 100644 index 0000000..05581f3 --- /dev/null +++ b/submit/final/q2/deep_learning/q2/mofe.py @@ -0,0 +1,4 @@ +"""Compatibility import for the maintained model registry.""" +from ....model.mofe import EXPERT_NAMES, SUBSETS, MixtureOfFusionExperts + +__all__ = ["EXPERT_NAMES", "SUBSETS", "MixtureOfFusionExperts"] diff --git a/submit/final/q2/deep_learning/q2/train_compare.py b/submit/final/q2/deep_learning/q2/train_compare.py new file mode 100644 index 0000000..7716b60 --- /dev/null +++ b/submit/final/q2/deep_learning/q2/train_compare.py @@ -0,0 +1,490 @@ +from __future__ import annotations + +import argparse +import csv +import hashlib +import json +import math +import random +import shutil +import time +from collections import Counter +from pathlib import Path +from typing import Any + +import matplotlib.pyplot as plt +import numpy as np +import torch +import torch.nn.functional as F +from sklearn.metrics import accuracy_score, f1_score, mean_absolute_error +from torch import nn + +from .data import ( + ATTACHMENT2, + ROOT, + MODALITIES, + RobustStats, + Split, + apply_robust_stats, + augment_masks, + corrupt_masks, + fit_robust_stats, + load_aligned, + load_fixed_window, + shift_audio_vision, +) +from .models import AlignedFusionModel + + +PATTERNS = { + "text": (0,), + "audio": (1,), + "vision": (2,), + "audio_vision": (1, 2), + "all_modalities": (0, 1, 2), +} +KINDS = ("concat",) + + +def seed_everything(seed: int) -> None: + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + + +def _tensor_split(split: Split, device: torch.device) -> tuple[tuple[torch.Tensor, ...], torch.Tensor, torch.Tensor, torch.Tensor]: + xs = tuple(torch.as_tensor(x, dtype=torch.float32, device=device) for x in split.x) + mask = torch.as_tensor(split.mask, dtype=torch.bool, device=device) + y_cls = torch.as_tensor(split.y_cls, dtype=torch.long, device=device) + y_reg = torch.as_tensor(split.y_reg, dtype=torch.float32, device=device) + return xs, mask, y_cls, y_reg + + +def _loss(output: dict[str, torch.Tensor], y_cls: torch.Tensor, y_reg: torch.Tensor) -> torch.Tensor: + class_loss = F.cross_entropy(output["logits"], y_cls) + intensity_loss = F.smooth_l1_loss(output["intensity"] / 3.0, y_reg / 3.0) + return class_loss + 0.5 * intensity_loss + + +@torch.inference_mode() +def _score_arrays( + model: AlignedFusionModel, + split: Split, + mask: np.ndarray, + device: torch.device, + batch_size: int = 128, +) -> tuple[dict[str, float], dict[str, np.ndarray]]: + model.eval() + predictions: dict[str, list[np.ndarray]] = {"logits": [], "intensity": []} + xs = split.x + for start in range(0, split.n, batch_size): + end = min(start + batch_size, split.n) + xb = tuple(torch.as_tensor(x[start:end], dtype=torch.float32, device=device) for x in xs) + mb = torch.as_tensor(mask[start:end], dtype=torch.bool, device=device) + output = model(xb, mb) + predictions["logits"].append(output["logits"].float().cpu().numpy()) + predictions["intensity"].append(output["intensity"].float().cpu().numpy()) + logits = np.concatenate(predictions["logits"], axis=0) + intensity = np.clip(np.concatenate(predictions["intensity"], axis=0), -3.0, 3.0) + pred_cls = logits.argmax(axis=-1) + pearson = _pearson(split.y_reg, intensity) + metrics = { + "accuracy": float(accuracy_score(split.y_cls, pred_cls)), + "macro_f1": float(f1_score(split.y_cls, pred_cls, labels=[0, 1, 2], average="macro", zero_division=0)), + "mae": float(mean_absolute_error(split.y_reg, intensity)), + "pearson": pearson, + } + return metrics, {"logits": logits, "intensity": intensity, "class": pred_cls} + + +def _pearson(y: np.ndarray, pred: np.ndarray) -> float: + a = np.asarray(y, dtype=np.float64) + b = np.asarray(pred, dtype=np.float64) + if a.std() < 1e-12 or b.std() < 1e-12: + return 0.0 + return float(np.corrcoef(a, b)[0, 1]) + + +def _validation_loss(model: AlignedFusionModel, valid: Split, device: torch.device, batch_size: int) -> float: + model.eval() + xs, masks, y_cls, y_reg = _tensor_split(valid, device) + losses: list[float] = [] + with torch.inference_mode(): + for start in range(0, valid.n, batch_size): + idx = slice(start, min(start + batch_size, valid.n)) + output = model(tuple(x[idx] for x in xs), masks[idx]) + losses.append(float(_loss(output, y_cls[idx], y_reg[idx]).item())) + return float(np.average(losses, weights=[min(batch_size, valid.n - i) for i in range(0, valid.n, batch_size)])) + + +def _write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + if not rows: + return + fields = list(dict.fromkeys(key for row in rows for key in row)) + with path.open("w", newline="", encoding="utf-8-sig") as stream: + writer = csv.DictWriter(stream, fieldnames=fields) + writer.writeheader() + writer.writerows(rows) + + +def _train_one( + kind: str, + train: Split, + valid: Split, + output_dir: Path, + device: torch.device, + seed: int, + epochs: int, + patience: int, + batch_size: int, +) -> tuple[AlignedFusionModel, int, list[dict[str, float]]]: + seed_everything(seed) + dims = tuple(int(x.shape[-1]) for x in train.x) + model = AlignedFusionModel(kind, dims=dims).to(device) + optimizer = torch.optim.AdamW(model.parameters(), lr=1.5e-4, weight_decay=1e-4) + train_tensors = _tensor_split(train, device) + xs, base_masks, y_cls, y_reg = train_tensors + rng = np.random.default_rng(seed + 809) + best_loss = math.inf + best_epoch = 0 + stale_epochs = 0 + history: list[dict[str, float]] = [] + checkpoint_path = output_dir / "model_best.pt" + output_dir.mkdir(parents=True, exist_ok=True) + + for epoch in range(1, epochs + 1): + model.train() + order = rng.permutation(train.n) + batch_losses: list[float] = [] + for start in range(0, train.n, batch_size): + ids_np = order[start:start + batch_size] + ids = torch.as_tensor(ids_np, dtype=torch.long, device=device) + masks_np = augment_masks(train.mask[ids_np], rng) + masks = torch.as_tensor(masks_np, dtype=torch.bool, device=device) + output = model(tuple(x.index_select(0, ids) for x in xs), masks) + loss = _loss(output, y_cls.index_select(0, ids), y_reg.index_select(0, ids)) + optimizer.zero_grad(set_to_none=True) + loss.backward() + nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) + optimizer.step() + batch_losses.append(float(loss.detach().item())) + valid_loss = _validation_loss(model, valid, device, batch_size) + row = {"epoch": float(epoch), "train_loss": float(np.mean(batch_losses)), "valid_clean_loss": valid_loss} + history.append(row) + print(f"[{kind}] epoch={epoch:02d} train={row['train_loss']:.4f} valid={valid_loss:.4f}", flush=True) + if valid_loss < best_loss - 1e-4: + best_loss = valid_loss + best_epoch = epoch + stale_epochs = 0 + torch.save({"kind": kind, "dims": dims, "state_dict": model.state_dict(), "seed": seed, "best_epoch": epoch}, checkpoint_path) + else: + stale_epochs += 1 + if stale_epochs >= patience: + break + + saved = torch.load(checkpoint_path, map_location=device, weights_only=False) + model.load_state_dict(saved["state_dict"]) + model.eval() + _write_csv(output_dir / "training_history.csv", history) + return model, best_epoch, history + + +def _conditions(valid: Split, seed: int) -> list[tuple[str, float, np.ndarray]]: + result = [("clean", 0.0, valid.mask.copy())] + for rate in (0.10, 0.20, 0.30): + for pattern_id, (pattern, mods) in enumerate(PATTERNS.items()): + result.append((pattern, rate, corrupt_masks(valid.mask, rate, mods, seed + pattern_id * 101 + int(rate * 1000)))) + return result + + +def _eval_conditions( + model: AlignedFusionModel, + valid: Split, + device: torch.device, + seed: int, + seed_run: int, + method: str, + representation: str, +) -> list[dict[str, Any]]: + rows = [] + for condition, rate, masks in _conditions(valid, seed): + metrics, _ = _score_arrays(model, valid, masks, device) + rows.append({"method": method, "representation": representation, "seed": seed_run, "condition": condition, + "missing_rate": rate, "n_valid": valid.n, **metrics}) + print(f"[{method}/{representation}] {condition:14s} rate={rate:.1f} " + f"F1={metrics['macro_f1']:.3f} MAE={metrics['mae']:.3f} " + f"P={metrics['pearson']:.3f}", flush=True) + return rows + + +def _summary(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: + groups = list(dict.fromkeys((row["method"], row["representation"]) for row in rows)) + summary: list[dict[str, Any]] = [] + for method, representation in groups: + matching = [r for r in rows if r["method"] == method and r["representation"] == representation] + local = [r for r in matching if r["condition"] != "clean" and r["missing_rate"] > 0] + clean = [r for r in matching if r["condition"] == "clean"] + seeds = sorted({int(r.get("seed", 0)) for r in matching}) + + def per_seed_mean(selected: list[dict[str, Any]], metric: str) -> list[float]: + return [float(np.mean([r[metric] for r in selected if int(r.get("seed", 0)) == seed])) + for seed in seeds if any(int(r.get("seed", 0)) == seed for r in selected)] + + clean_f1 = per_seed_mean(clean, "macro_f1") + clean_accuracy = per_seed_mean(clean, "accuracy") + clean_mae = per_seed_mean(clean, "mae") + clean_pearson = per_seed_mean(clean, "pearson") + corrupt_f1 = per_seed_mean(local, "macro_f1") + corrupt_accuracy = per_seed_mean(local, "accuracy") + corrupt_mae = per_seed_mean(local, "mae") + corrupt_pearson = per_seed_mean(local, "pearson") + row: dict[str, Any] = { + "method": method, + "representation": representation, + "n_seeds": len(seeds), + "clean_accuracy": float(np.mean(clean_accuracy)), + "clean_accuracy_sd": float(np.std(clean_accuracy, ddof=1)) if len(clean_accuracy) > 1 else 0.0, + "clean_macro_f1": float(np.mean(clean_f1)), + "clean_macro_f1_sd": float(np.std(clean_f1, ddof=1)) if len(clean_f1) > 1 else 0.0, + "clean_mae": float(np.mean(clean_mae)), + "clean_mae_sd": float(np.std(clean_mae, ddof=1)) if len(clean_mae) > 1 else 0.0, + "clean_pearson": float(np.mean(clean_pearson)), + "clean_pearson_sd": float(np.std(clean_pearson, ddof=1)) if len(clean_pearson) > 1 else 0.0, + "corrupt_accuracy_mean": float(np.mean(corrupt_accuracy)), + "corrupt_accuracy_sd": float(np.std(corrupt_accuracy, ddof=1)) if len(corrupt_accuracy) > 1 else 0.0, + "corrupt_macro_f1_mean": float(np.mean(corrupt_f1)), + "corrupt_macro_f1_sd": float(np.std(corrupt_f1, ddof=1)) if len(corrupt_f1) > 1 else 0.0, + "corrupt_macro_f1_worst": float(np.min([r["macro_f1"] for r in local])), + "corrupt_mae_mean": float(np.mean(corrupt_mae)), + "corrupt_mae_sd": float(np.std(corrupt_mae, ddof=1)) if len(corrupt_mae) > 1 else 0.0, + "corrupt_pearson_mean": float(np.mean(corrupt_pearson)), + "corrupt_pearson_sd": float(np.std(corrupt_pearson, ddof=1)) if len(corrupt_pearson) > 1 else 0.0, + } + for rate in (0.10, 0.20, 0.30): + at_rate = [r for r in local if r["missing_rate"] == rate] + f1_by_seed = per_seed_mean(at_rate, "macro_f1") + accuracy_by_seed = per_seed_mean(at_rate, "accuracy") + mae_by_seed = per_seed_mean(at_rate, "mae") + row[f"f1_rate_{int(rate * 100)}"] = float(np.mean(f1_by_seed)) + row[f"accuracy_rate_{int(rate * 100)}"] = float(np.mean(accuracy_by_seed)) + row[f"mae_rate_{int(rate * 100)}"] = float(np.mean(mae_by_seed)) + summary.append(row) + for row in summary: + row["pareto_nondominated"] = not any( + other is not row and other["representation"] == row["representation"] + and other["corrupt_macro_f1_mean"] >= row["corrupt_macro_f1_mean"] + and other["corrupt_mae_mean"] <= row["corrupt_mae_mean"] + and other["corrupt_pearson_mean"] >= row["corrupt_pearson_mean"] + and ( + other["corrupt_macro_f1_mean"] > row["corrupt_macro_f1_mean"] + or other["corrupt_mae_mean"] < row["corrupt_mae_mean"] + or other["corrupt_pearson_mean"] > row["corrupt_pearson_mean"] + ) + for other in summary + ) + return summary + + +def _plot(summary: list[dict[str, Any]], rows: list[dict[str, Any]], path: Path) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + colors = {"concat": "#4e79a7"} + fig, axes = plt.subplots(1, 2, figsize=(11, 4.4), constrained_layout=True) + for row in summary: + kind = row["method"] + y_f1 = [row["clean_macro_f1"]] + [row[f"f1_rate_{r}"] for r in (10, 20, 30)] + y_mae = [row["clean_mae"]] + [row[f"mae_rate_{r}"] for r in (10, 20, 30)] + axes[0].plot([0, 10, 20, 30], y_f1, marker="o", label=kind, color=colors.get(kind)) + axes[1].plot([0, 10, 20, 30], y_mae, marker="o", label=kind, color=colors.get(kind)) + axes[0].set(title="Polarity under contiguous local missingness", xlabel="masked slots (%)", ylabel="Macro-F1 (higher is better)") + axes[1].set(title="Intensity under contiguous local missingness", xlabel="masked slots (%)", ylabel="MAE (lower is better)") + for ax in axes: + ax.grid(alpha=0.25) + ax.legend(frameon=False) + fig.savefig(path, dpi=180) + plt.close(fig) + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def _run(args: argparse.Namespace) -> None: + seed_everything(args.seeds[0]) + if args.device == "auto": + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + else: + device = torch.device(args.device) + torch.set_num_threads(args.threads) + output = Path(args.output_dir) + output.mkdir(parents=True, exist_ok=True) + aligned_raw = load_aligned() + stats = fit_robust_stats(aligned_raw["train"]) + stats.save(output / "aligned_robust_stats.npz") + aligned = {k: apply_robust_stats(v, stats) for k, v in aligned_raw.items()} + audit = { + "source": str(ATTACHMENT2 / "aligned_50.pkl"), + "train_samples": aligned["train"].n, + "valid_samples": aligned["valid"].n, + "train_classes": np.bincount(aligned["train"].y_cls, minlength=3).tolist(), + "valid_classes": np.bincount(aligned["valid"].y_cls, minlength=3).tolist(), + "mean_observed_slots": { + MODALITIES[m]: float(aligned["train"].mask[:, :, m].sum(axis=1).mean()) for m in range(3) + }, + "train_valid_video_overlap": 0, + } + with (output / "data_audit.json").open("w", encoding="utf-8") as stream: + json.dump(audit, stream, ensure_ascii=False, indent=2) + print(f"device={device}; train={audit['train_samples']}; valid={audit['valid_samples']}; audit={audit}", flush=True) + + metric_rows: list[dict[str, Any]] = [] + best_epochs: dict[str, int] = {} + for kind in KINDS: + for seed in args.seeds: + seed_dir = output / "models" / "aligned" / kind / f"seed_{seed}" + model, best_epoch, _ = _train_one( + kind, aligned["train"], aligned["valid"], seed_dir, + device, seed, args.epochs, args.patience, args.batch_size, + ) + best_epochs[f"{kind}_seed_{seed}"] = best_epoch + metric_rows.extend(_eval_conditions(model, aligned["valid"], device, seed + 13, seed, kind, "provided_word_aligned_50")) + if seed == args.seeds[0]: + shutil.copy2(seed_dir / "model_best.pt", output / "models" / "aligned" / kind / "model_best.pt") + del model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + summary = _summary(metric_rows) + selected = sorted(summary, key=lambda r: (-r["corrupt_macro_f1_mean"], r["corrupt_mae_mean"], r["method"]))[0]["method"] + (output / "selected_method.txt").write_text( + f"Macro-F1-first validation selection: {selected}. See summary.csv for the full multi-metric tradeoff.\n", + encoding="utf-8", + ) + + # Matched audio/vision temporal-shift control for the selected architecture and every seed. + for seed in args.seeds: + aligned_payload = torch.load(output / "models" / "aligned" / selected / f"seed_{seed}" / "model_best.pt", + map_location=device, weights_only=False) + aligned_model = AlignedFusionModel(selected, tuple(aligned_payload["dims"])).to(device) + aligned_model.load_state_dict(aligned_payload["state_dict"]) + shifted = shift_audio_vision(aligned["valid"], seed=seed + 2026, max_shift=10) + shift_metrics, _ = _score_arrays(aligned_model, shifted, shifted.mask, device) + metric_rows.append({"method": selected, "representation": "provided_word_aligned_50", "seed": seed, + "condition": "audio_vision_shifted_1_to_10_slots", "missing_rate": 0.0, + "n_valid": shifted.n, **shift_metrics}) + del aligned_model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + # Same selected fusion architecture, but equal-window audio/vision pooling of the unaligned source. + print(f"selected_by_corrupt_macro_f1={selected}; starting fixed-window alignment control", flush=True) + fixed_raw = load_fixed_window() + fixed_stats = fit_robust_stats(fixed_raw["train"]) + fixed_stats.save(output / "fixed_window_robust_stats.npz") + fixed = {k: apply_robust_stats(v, fixed_stats) for k, v in fixed_raw.items()} + for seed in args.seeds: + fixed_model, fixed_epoch, _ = _train_one( + selected, fixed["train"], fixed["valid"], output / "models" / "fixed_window" / selected / f"seed_{seed}", + device, seed, args.epochs, args.patience, args.batch_size, + ) + best_epochs[f"fixed_window_{selected}_seed_{seed}"] = fixed_epoch + metric_rows.extend(_eval_conditions(fixed_model, fixed["valid"], device, seed + 13, seed, selected, + "equal_window_resampled_unaligned")) + fixed_shifted = shift_audio_vision(fixed["valid"], seed=seed + 2026, max_shift=10) + fixed_shift_metrics, _ = _score_arrays(fixed_model, fixed_shifted, fixed_shifted.mask, device) + metric_rows.append({"method": selected, "representation": "equal_window_resampled_unaligned", "seed": seed, + "condition": "audio_vision_shifted_1_to_10_slots", "missing_rate": 0.0, + "n_valid": fixed_shifted.n, **fixed_shift_metrics}) + del fixed_model + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + all_summary = _summary(metric_rows) + _write_csv(output / "validation_metrics_by_condition.csv", metric_rows) + _write_csv(output / "summary.csv", all_summary) + aligned_summary = [r for r in all_summary if r["representation"] == "provided_word_aligned_50"] + _plot(aligned_summary, metric_rows, output / "missing_rate_comparison.png") + alignment_rows = [] + for rep in ("provided_word_aligned_50", "equal_window_resampled_unaligned"): + for condition in ("clean", "audio_vision_shifted_1_to_10_slots"): + match = [r for r in metric_rows if r["method"] == selected and r["representation"] == rep + and r["condition"] == condition] + if match: + row = {"method": selected, "representation": rep, "condition": condition, + "n_valid": aligned["valid"].n, "n_seeds": len(match)} + for metric in ("accuracy", "macro_f1", "mae", "pearson"): + values = [r[metric] for r in match] + row[metric] = float(np.mean(values)) + row[f"{metric}_sd"] = float(np.std(values, ddof=1)) if len(values) > 1 else 0.0 + alignment_rows.append(row) + corrupt = [r for r in metric_rows if r["method"] == selected and r["representation"] == rep + and r["condition"] != "clean" and r["missing_rate"] > 0] + if corrupt: + per_seed = [] + for seed in args.seeds: + local = [r for r in corrupt if int(r["seed"]) == seed] + if local: + per_seed.append({metric: float(np.mean([r[metric] for r in local])) for metric in + ("accuracy", "macro_f1", "mae", "pearson")}) + alignment_rows.append({ + "method": selected, "representation": rep, "condition": "all_local_corruption_mean", + "missing_rate": float(np.mean([r["missing_rate"] for r in corrupt])), + "n_valid": aligned["valid"].n, "n_seeds": len(per_seed), + **{metric: float(np.mean([r[metric] for r in per_seed])) for metric in ("accuracy", "macro_f1", "mae", "pearson")}, + **{f"{metric}_sd": float(np.std([r[metric] for r in per_seed], ddof=1)) if len(per_seed) > 1 else 0.0 + for metric in ("accuracy", "macro_f1", "mae", "pearson")}, + }) + _write_csv(output / "alignment_transfer_ablation.csv", alignment_rows) + + source_path = ATTACHMENT2 / "aligned_50.pkl" + manifest = { + "source_feature": str(source_path), + "source_sha256": _sha256(source_path), + "device": str(device), + "cuda_name": torch.cuda.get_device_name(0) if device.type == "cuda" else None, + "seeds": args.seeds, + "epochs_max": args.epochs, + "patience": args.patience, + "batch_size": args.batch_size, + "best_epochs": best_epochs, + "selected_macro_f1_first": selected, + "selection_policy": "report Macro-F1, MAE, and Pearson separately; selected model maximizes mean validation Macro-F1 across 15 contiguous corruption conditions, then uses MAE and lexical model name only as tie-breaks", + "models": list(KINDS), + "corruption_rates": [0.10, 0.20, 0.30], + "corruption_patterns": list(PATTERNS), + "feature_scaling": "training split median/MAD; fallback to standard deviation for zero-MAD dimensions", + "test_labels_used": False, + "alignment_transfer_limit": "The official aligned_50 data use a 50-slot wordpiece sequence with no per-slot seconds or stored Q1 B1 time_bounds. The fixed-window comparison is a downstream alignment control, not a re-run of Q1 B1 on the full dataset.", + "python": __import__("sys").version, + "torch": torch.__version__, + "numpy": np.__version__, + "created_unix": time.time(), + } + with (output / "run_manifest.json").open("w", encoding="utf-8") as stream: + json.dump(manifest, stream, ensure_ascii=False, indent=2) + print(f"saved selection artifacts to {output}; selected={selected}; seeds={args.seeds}", flush=True) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Train the EarlyConcat baseline and its alignment-transfer control") + parser.add_argument("--seeds", type=int, nargs="+", default=[42, 3407, 2026]) + parser.add_argument("--epochs", type=int, default=32) + parser.add_argument("--patience", type=int, default=6) + parser.add_argument("--batch-size", type=int, default=64) + parser.add_argument("--threads", type=int, default=4) + parser.add_argument("--device", default="auto") + parser.add_argument("--output-dir", default=str(Path(__file__).resolve().parents[1] / "outputs" / "followups" / "earlyconcat_standalone")) + args = parser.parse_args() + _run(args) + + +if __name__ == "__main__": + main() diff --git a/submit/final/q2/deep_learning/q2/train_mofe.py b/submit/final/q2/deep_learning/q2/train_mofe.py new file mode 100644 index 0000000..a22b6d4 --- /dev/null +++ b/submit/final/q2/deep_learning/q2/train_mofe.py @@ -0,0 +1,787 @@ +from __future__ import annotations + +import argparse +import csv +import hashlib +import json +import math +import random +import sys +import time +from pathlib import Path +from typing import Any + +import numpy as np +import torch +from sklearn.metrics import accuracy_score, f1_score, mean_absolute_error +from torch import nn + +from .data import ( + ATTACHMENT2, + MODALITIES, + RobustStats, + Split, + apply_robust_stats, + augment_masks, + corrupt_masks, + fit_robust_stats, + load_aligned, +) +from .models import AlignedFusionModel +from .mofe import EXPERT_NAMES, SUBSETS, MixtureOfFusionExperts +from .train_compare import PATTERNS, _loss, _pearson, _train_one, seed_everything + + +ROOT = Path(__file__).resolve().parents[1] +REFERENCE_OUTPUT = ROOT / "outputs" / "mofe_7experts" +DEFAULT_OUTPUT = ROOT / "outputs" / "mofe_7experts" +EARLYCONCAT = "B0_early_concat" +MOFE7_MLP = "B5_mofe_mlp" +SEEDS = (42, 3407, 2026) +RATES = (0.10, 0.20, 0.30) +HIDDEN = 128 +LATENT_DIM = 64 +MODEL_CONFIG: dict[str, Any] = { + "router": "mlp", + "expert_names": EXPERT_NAMES, + "availability_mode": "hard", +} +SUMMARY_METRICS = ( + "corrupt_macro_f1", + "worst_condition_macro_f1", + "text_30_macro_f1", + "corrupt_mae", + "corrupt_pearson", +) + + +def _write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + if not rows: + return + fields = list(dict.fromkeys(key for row in rows for key in row)) + with path.open("w", newline="", encoding="utf-8-sig") as stream: + writer = csv.DictWriter(stream, fieldnames=fields) + writer.writeheader() + writer.writerows(rows) + + +def _read_csv(path: Path) -> list[dict[str, str]]: + if not path.exists(): + return [] + with path.open("r", newline="", encoding="utf-8-sig") as stream: + return list(csv.DictReader(stream)) + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def _device_for(name: str) -> torch.device: + if name == "auto": + return torch.device("cuda" if torch.cuda.is_available() else "cpu") + return torch.device(name) + + +def _conditions(valid: Split, seed: int) -> list[tuple[str, float, np.ndarray]]: + rows = [("clean", 0.0, valid.mask.copy())] + for rate in RATES: + for pattern_idx, (pattern, modalities) in enumerate(PATTERNS.items()): + masks = corrupt_masks( + valid.mask, + rate, + modalities, + seed + 13 + pattern_idx * 101 + int(rate * 1000), + ) + rows.append((f"{pattern}_{int(rate * 100)}", rate, masks)) + return rows + + +def _metric_dict( + y_cls: np.ndarray, + y_reg: np.ndarray, + logits: np.ndarray, + intensity: np.ndarray, +) -> dict[str, float]: + predicted_class = np.asarray(logits).argmax(axis=-1) + predicted_intensity = np.clip(np.asarray(intensity).reshape(-1), -3.0, 3.0) + return { + "accuracy": float(accuracy_score(y_cls, predicted_class)), + "macro_f1": float(f1_score(y_cls, predicted_class, labels=[0, 1, 2], average="macro", zero_division=0)), + "mae": float(mean_absolute_error(y_reg, predicted_intensity)), + "pearson": _pearson(y_reg, predicted_intensity), + } + + +def _validation_loss(model: nn.Module, valid: Split, device: torch.device, batch_size: int) -> float: + model.eval() + values: list[float] = [] + weights: list[int] = [] + with torch.inference_mode(): + for start in range(0, valid.n, batch_size): + end = min(start + batch_size, valid.n) + xs = tuple(torch.as_tensor(x[start:end], dtype=torch.float32, device=device) for x in valid.x) + masks = torch.as_tensor(valid.mask[start:end], dtype=torch.bool, device=device) + y_cls = torch.as_tensor(valid.y_cls[start:end], dtype=torch.long, device=device) + y_reg = torch.as_tensor(valid.y_reg[start:end], dtype=torch.float32, device=device) + values.append(float(_loss(model(xs, masks), y_cls, y_reg).item())) + weights.append(end - start) + return float(np.average(values, weights=weights)) + + +def _train_mofe( + train: Split, + valid: Split, + output_dir: Path, + device: torch.device, + seed: int, + epochs: int, + patience: int, + batch_size: int, + reuse_checkpoint: bool, +) -> tuple[MixtureOfFusionExperts, int, list[dict[str, Any]]]: + dims = tuple(int(x.shape[-1]) for x in train.x) + checkpoint_path = output_dir / "model_best.pt" + history_path = output_dir / "training_history.csv" + if reuse_checkpoint and checkpoint_path.exists(): + saved = torch.load(checkpoint_path, map_location=device, weights_only=False) + if saved.get("config") != MODEL_CONFIG or tuple(saved.get("dims", ())) != dims or int(saved.get("seed", -1)) != seed: + raise ValueError(f"cached MoFE checkpoint does not match the selected configuration: {checkpoint_path}") + model = MixtureOfFusionExperts(dims=dims, **MODEL_CONFIG).to(device) + model.load_state_dict(saved["state_dict"]) + history = [ + {"method": MOFE7_MLP, "seed": seed, **{key: float(value) for key, value in row.items() if key in {"epoch", "train_loss", "valid_clean_loss"}}} + for row in _read_csv(history_path) + ] + return model.eval(), int(saved.get("best_epoch", 0)), history + + output_dir.mkdir(parents=True, exist_ok=True) + seed_everything(seed) + model = MixtureOfFusionExperts(dims=dims, **MODEL_CONFIG).to(device) + optimizer = torch.optim.AdamW(model.parameters(), lr=1.5e-4, weight_decay=1e-4) + xs = tuple(torch.as_tensor(x, dtype=torch.float32, device=device) for x in train.x) + base_masks = train.mask + y_cls = torch.as_tensor(train.y_cls, dtype=torch.long, device=device) + y_reg = torch.as_tensor(train.y_reg, dtype=torch.float32, device=device) + rng = np.random.default_rng(seed + 809) + best_loss = math.inf + best_epoch = 0 + stale_epochs = 0 + history: list[dict[str, Any]] = [] + + for epoch in range(1, epochs + 1): + model.train() + order = rng.permutation(train.n) + batch_losses: list[float] = [] + for start in range(0, train.n, batch_size): + ids_np = order[start:start + batch_size] + ids = torch.as_tensor(ids_np, dtype=torch.long, device=device) + masks_np = augment_masks(base_masks[ids_np], rng) + masks = torch.as_tensor(masks_np, dtype=torch.bool, device=device) + output = model(tuple(x.index_select(0, ids) for x in xs), masks) + loss = _loss(output, y_cls.index_select(0, ids), y_reg.index_select(0, ids)) + optimizer.zero_grad(set_to_none=True) + loss.backward() + nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) + optimizer.step() + batch_losses.append(float(loss.detach().item())) + + valid_loss = _validation_loss(model, valid, device, batch_size) + row = { + "method": MOFE7_MLP, + "seed": seed, + "epoch": epoch, + "train_loss": float(np.mean(batch_losses)), + "valid_clean_loss": valid_loss, + } + history.append(row) + print(f"[MoFE-7 MLP] seed={seed} epoch={epoch:02d} train={row['train_loss']:.4f} valid={valid_loss:.4f}", flush=True) + if valid_loss < best_loss - 1e-4: + best_loss = valid_loss + best_epoch = epoch + stale_epochs = 0 + torch.save({ + "method": MOFE7_MLP, + "config": MODEL_CONFIG, + "dims": dims, + "state_dict": model.state_dict(), + "seed": seed, + "best_epoch": epoch, + }, checkpoint_path) + else: + stale_epochs += 1 + if stale_epochs >= patience: + break + + saved = torch.load(checkpoint_path, map_location=device, weights_only=False) + model.load_state_dict(saved["state_dict"]) + model.eval() + _write_csv(history_path, history) + return model, best_epoch, history + + +def _load_or_train_concat( + train: Split, + valid: Split, + output_dir: Path, + device: torch.device, + seed: int, + epochs: int, + patience: int, + batch_size: int, + reuse_checkpoint: bool, +) -> tuple[AlignedFusionModel, int, list[dict[str, Any]]]: + checkpoint_path = output_dir / "model_best.pt" + dims = tuple(int(x.shape[-1]) for x in train.x) + if reuse_checkpoint and checkpoint_path.exists(): + saved = torch.load(checkpoint_path, map_location=device, weights_only=False) + if saved.get("kind") != "concat" or tuple(saved.get("dims", ())) != dims or int(saved.get("seed", -1)) != seed: + raise ValueError(f"cached EarlyConcat checkpoint does not match: {checkpoint_path}") + model = AlignedFusionModel("concat", dims=dims).to(device) + model.load_state_dict(saved["state_dict"]) + history = [ + {"method": EARLYCONCAT, "seed": seed, **{key: float(value) for key, value in row.items() if key in {"epoch", "train_loss", "valid_clean_loss"}}} + for row in _read_csv(output_dir / "training_history.csv") + ] + return model.eval(), int(saved.get("best_epoch", 0)), history + + model, best_epoch, history = _train_one( + "concat", train, valid, output_dir, device, seed, epochs, patience, batch_size + ) + rows = [{"method": EARLYCONCAT, "seed": seed, **row} for row in history] + return model.eval(), best_epoch, rows + + +@torch.inference_mode() +def _predict( + model: nn.Module, + split: Split, + masks: np.ndarray, + device: torch.device, + batch_size: int, + force_expert: str | None = None, +) -> dict[str, np.ndarray]: + fields = ["logits", "intensity"] + if isinstance(model, MixtureOfFusionExperts): + fields.extend(("alpha", "utility", "availability", "fallback")) + chunks: dict[str, list[np.ndarray]] = {name: [] for name in fields} + for start in range(0, split.n, batch_size): + end = min(start + batch_size, split.n) + xs = tuple(torch.as_tensor(x[start:end], dtype=torch.float32, device=device) for x in split.x) + mask_batch = torch.as_tensor(masks[start:end], dtype=torch.bool, device=device) + output = model(xs, mask_batch, force_expert=force_expert) if isinstance(model, MixtureOfFusionExperts) else model(xs, mask_batch) + for name in fields: + value = output[name] + chunks[name].append(value.float().cpu().numpy()) + result = {name: np.concatenate(values, axis=0) for name, values in chunks.items()} + result["intensity"] = np.clip(result["intensity"].reshape(-1), -3.0, 3.0) + return result + + +def _condition_row( + method: str, + seed: int, + condition: str, + rate: float, + split: Split, + prediction: dict[str, np.ndarray], +) -> dict[str, Any]: + return { + "method": method, + "seed": seed, + "condition": condition, + "missing_rate": rate, + "n_valid": split.n, + **_metric_dict(split.y_cls, split.y_reg, prediction["logits"], prediction["intensity"]), + } + + +def _diagnostics( + seed: int, + condition: str, + masks: np.ndarray, + prediction: dict[str, np.ndarray], +) -> tuple[dict[str, Any], dict[str, Any]]: + alpha = prediction["alpha"] + availability = prediction["availability"].astype(bool) + active = availability.any(axis=-1) + active_alpha = alpha[active] + if active_alpha.size: + means = active_alpha.mean(axis=0) + entropy = -(active_alpha * np.log(np.maximum(active_alpha, 1e-12))).sum(axis=-1) / np.log(len(EXPERT_NAMES)) + high_weight = (active_alpha.max(axis=-1) > 0.8).mean() + else: + means = np.zeros(len(EXPERT_NAMES), dtype=np.float64) + entropy = np.zeros(0, dtype=np.float64) + high_weight = 0.0 + route_row: dict[str, Any] = { + "method": MOFE7_MLP, + "seed": seed, + "condition": condition, + "active_position_fraction": float(active.mean()), + "fallback_position_fraction": float((~active).mean()), + "normalized_router_entropy": float(entropy.mean()) if entropy.size else 0.0, + "fraction_active_positions_max_weight_over_0p8": float(high_weight), + } + for index, name in enumerate(EXPERT_NAMES): + route_row[f"alpha_{name}_mean"] = float(means[index]) + utility = prediction["utility"] + utility_row: dict[str, Any] = {"method": MOFE7_MLP, "seed": seed, "condition": condition} + for modality, name in enumerate(MODALITIES): + observed = masks[..., modality] + utility_row[f"utility_{name}_mean"] = float(utility[..., modality][observed].mean()) if observed.any() else 0.0 + return route_row, utility_row + + +def _summary_rows(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: + summaries: list[dict[str, Any]] = [] + for method in (EARLYCONCAT, MOFE7_MLP): + matching = [row for row in rows if row["method"] == method] + seeds = sorted({int(row["seed"]) for row in matching}) + conditions = list(dict.fromkeys(row["condition"] for row in matching)) + by_seed_condition = {(int(row["seed"]), row["condition"]): row for row in matching} + clean = [by_seed_condition[(seed, "clean")] for seed in seeds] + corrupt_conditions = [condition for condition in conditions if condition != "clean"] + corrupt_by_seed = { + seed: [by_seed_condition[(seed, condition)] for condition in corrupt_conditions] + for seed in seeds + } + condition_f1 = { + condition: float(np.mean([by_seed_condition[(seed, condition)]["macro_f1"] for seed in seeds])) + for condition in corrupt_conditions + } + worst_condition = min(condition_f1, key=condition_f1.get) + row: dict[str, Any] = {"method": method, "n_seeds": len(seeds), "worst_condition": worst_condition} + for metric in ("accuracy", "macro_f1", "mae", "pearson"): + clean_values = [float(item[metric]) for item in clean] + corrupt_values = [float(np.mean([item[metric] for item in corrupt_by_seed[seed]])) for seed in seeds] + row[f"clean_{metric}"] = float(np.mean(clean_values)) + row[f"clean_{metric}_sd"] = float(np.std(clean_values, ddof=1)) if len(clean_values) > 1 else 0.0 + row[f"corrupt_{metric}_mean"] = float(np.mean(corrupt_values)) + row[f"corrupt_{metric}_sd"] = float(np.std(corrupt_values, ddof=1)) if len(corrupt_values) > 1 else 0.0 + row["worst_condition_macro_f1"] = condition_f1[worst_condition] + row["worst_single_run_macro_f1"] = min( + item["macro_f1"] for seed in seeds for item in corrupt_by_seed[seed] + ) + text_30 = [by_seed_condition[(seed, "text_30")] for seed in seeds] + row["text_30_macro_f1"] = float(np.mean([item["macro_f1"] for item in text_30])) + row["text_30_macro_f1_sd"] = float(np.std([item["macro_f1"] for item in text_30], ddof=1)) if len(text_30) > 1 else 0.0 + for condition in ("audio_30", "vision_30", "audio_vision_30", "all_modalities_30"): + values = [by_seed_condition[(seed, condition)]["macro_f1"] for seed in seeds] + row[f"{condition}_macro_f1"] = float(np.mean(values)) + row[f"{condition}_macro_f1_sd"] = float(np.std(values, ddof=1)) if len(values) > 1 else 0.0 + row["corrupt_macro_f1"] = row["corrupt_macro_f1_mean"] + row["corrupt_mae"] = row["corrupt_mae_mean"] + row["corrupt_pearson"] = row["corrupt_pearson_mean"] + summaries.append(row) + return summaries + + +def _bootstrap_distributions( + method: str, + predictions: dict[tuple[str, int, str], dict[str, np.ndarray]], + valid: Split, + seeds: list[int], + conditions: list[str], + group_counts: np.ndarray, +) -> dict[str, np.ndarray]: + group_names = sorted({sample_id.split("$_$", 1)[0] for sample_id in valid.ids}) + group_index = {name: index for index, name in enumerate(group_names)} + row_group = np.asarray([group_index[sample_id.split("$_$", 1)[0]] for sample_id in valid.ids], dtype=np.int64) + n_groups = len(group_names) + n_slots = len(seeds) * len(conditions) + confusion_by_group = np.zeros((n_groups, n_slots, 9), dtype=np.float64) + regression_by_group = np.zeros((n_groups, n_slots, 7), dtype=np.float64) + for seed_index, seed in enumerate(seeds): + for condition_index, condition in enumerate(conditions): + slot = seed_index * len(conditions) + condition_index + pred = predictions[(method, seed, condition)] + predicted_class = pred["logits"].argmax(axis=-1) + code = valid.y_cls * 3 + predicted_class + np.add.at(confusion_by_group[:, slot, :], (row_group, code), 1.0) + intensity = np.clip(pred["intensity"].reshape(-1), -3.0, 3.0) + values = np.stack(( + np.ones(valid.n), + np.abs(valid.y_reg - intensity), + valid.y_reg, + valid.y_reg ** 2, + intensity, + intensity ** 2, + valid.y_reg * intensity, + ), axis=-1) + for statistic in range(values.shape[-1]): + np.add.at(regression_by_group[:, slot, statistic], row_group, values[:, statistic]) + + weighted_confusion = np.einsum("rg,gsk->rsk", group_counts, confusion_by_group, optimize=True) + cm = weighted_confusion.reshape(len(group_counts), len(seeds), len(conditions), 3, 3) + true_count = cm.sum(axis=-1) + predicted_count = cm.sum(axis=-2) + true_positive = np.diagonal(cm, axis1=-2, axis2=-1) + denominator = true_count + predicted_count + class_f1 = np.divide(2.0 * true_positive, denominator, out=np.zeros_like(true_positive), where=denominator > 0) + macro_f1 = class_f1.mean(axis=-1) + + weighted_regression = np.einsum("rg,gsk->rsk", group_counts, regression_by_group, optimize=True) + regression = weighted_regression.reshape(len(group_counts), len(seeds), len(conditions), 7) + count = np.maximum(regression[..., 0], 1.0) + mae = regression[..., 1] / count + sum_y, sum_y2, sum_pred, sum_pred2, sum_yp = (regression[..., index] for index in range(2, 7)) + covariance = sum_yp - sum_y * sum_pred / count + variance_y = np.maximum(sum_y2 - sum_y ** 2 / count, 0.0) + variance_pred = np.maximum(sum_pred2 - sum_pred ** 2 / count, 0.0) + denominator_corr = np.sqrt(variance_y * variance_pred) + pearson = np.divide(covariance, denominator_corr, out=np.zeros_like(covariance), where=denominator_corr > 1e-12) + text_30_index = conditions.index("text_30") + return { + "corrupt_macro_f1": macro_f1[:, :, 1:].mean(axis=(1, 2)), + "worst_condition_macro_f1": macro_f1[:, :, 1:].mean(axis=1).min(axis=1), + "text_30_macro_f1": macro_f1[:, :, text_30_index].mean(axis=1), + "corrupt_mae": mae[:, :, 1:].mean(axis=(1, 2)), + "corrupt_pearson": pearson[:, :, 1:].mean(axis=(1, 2)), + } + + +def _paired_bootstrap( + predictions: dict[tuple[str, int, str], dict[str, np.ndarray]], + valid: Split, + seeds: list[int], + conditions: list[str], + reps: int, + bootstrap_seed: int, + summaries: list[dict[str, Any]], +) -> list[dict[str, Any]]: + groups = sorted({sample_id.split("$_$", 1)[0] for sample_id in valid.ids}) + rng = np.random.default_rng(bootstrap_seed) + draws = rng.integers(0, len(groups), size=(reps, len(groups))) + group_counts = np.zeros((reps, len(groups)), dtype=np.float64) + for rep in range(reps): + group_counts[rep] = np.bincount(draws[rep], minlength=len(groups)) + candidate = _bootstrap_distributions(MOFE7_MLP, predictions, valid, seeds, conditions, group_counts) + reference = _bootstrap_distributions(EARLYCONCAT, predictions, valid, seeds, conditions, group_counts) + summary_map = {row["method"]: row for row in summaries} + point_keys = { + "corrupt_macro_f1": "corrupt_macro_f1", + "worst_condition_macro_f1": "worst_condition_macro_f1", + "text_30_macro_f1": "text_30_macro_f1", + "corrupt_mae": "corrupt_mae", + "corrupt_pearson": "corrupt_pearson", + } + rows = [] + for metric in SUMMARY_METRICS: + delta = candidate[metric] - reference[metric] + key = point_keys[metric] + rows.append({ + "comparison": "MoFE-7 MLP vs EarlyConcat", + "candidate": MOFE7_MLP, + "reference": EARLYCONCAT, + "metric": metric, + "delta_candidate_minus_reference": float(summary_map[MOFE7_MLP][key] - summary_map[EARLYCONCAT][key]), + "bootstrap_ci_2p5": float(np.quantile(delta, 0.025)), + "bootstrap_ci_97p5": float(np.quantile(delta, 0.975)), + "bootstrap_probability_delta_gt_0": float(np.mean(delta > 0.0)), + "bootstrap_replicates": reps, + "resampling_unit": "source video id", + "paired": True, + "seed": bootstrap_seed, + }) + return rows + + +def _plot_summary(output: Path, summaries: list[dict[str, Any]]) -> None: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + labels = ["EarlyConcat + BiGRU", "MoFE-7 + MLP Router"] + by_method = {row["method"]: row for row in summaries} + methods = (EARLYCONCAT, MOFE7_MLP) + metrics = ("clean_macro_f1", "corrupt_macro_f1", "worst_condition_macro_f1") + names = ("Clean", "Mean corrupted", "Worst condition") + x = np.arange(len(names)) + width = 0.34 + fig, ax = plt.subplots(figsize=(8.6, 4.8), constrained_layout=True) + for offset, method, label, color in ( + (-width / 2, methods[0], labels[0], "#4e79a7"), + (width / 2, methods[1], labels[1], "#f28e2b"), + ): + values = [by_method[method][metric] for metric in metrics] + ax.bar(x + offset, values, width, label=label, color=color) + ax.set_xticks(x, names) + ax.set_ylabel("Macro-F1") + ax.set_ylim(0, 1) + ax.set_title("Q2 selected-model validation comparison") + ax.legend(frameon=False) + output.mkdir(parents=True, exist_ok=True) + fig.savefig(output / "comparison_earlyconcat_mofe7.png", dpi=180) + plt.close(fig) + + +def _parameter_rows(dims: tuple[int, int, int], device: torch.device) -> list[dict[str, Any]]: + models: dict[str, nn.Module] = { + EARLYCONCAT: AlignedFusionModel("concat", dims=dims), + MOFE7_MLP: MixtureOfFusionExperts(dims=dims, **MODEL_CONFIG), + } + baseline_count = sum(parameter.numel() for parameter in models[EARLYCONCAT].parameters() if parameter.requires_grad) + rows = [] + for name, model in models.items(): + count = sum(parameter.numel() for parameter in model.parameters() if parameter.requires_grad) + rows.append({ + "method": name, + "trainable_parameters": count, + "ratio_to_earlyconcat": count / baseline_count, + "within_2x_earlyconcat": bool(count <= 2 * baseline_count), + }) + return rows + + +def _smoke_test(train: Split, output: Path, device: torch.device, seed: int) -> dict[str, Any]: + seed_everything(seed) + dims = tuple(int(x.shape[-1]) for x in train.x) + count = min(4, train.n) + xs = tuple(torch.as_tensor(x[:count], dtype=torch.float32, device=device) for x in train.x) + masks = torch.as_tensor(train.mask[:count].copy(), dtype=torch.bool, device=device) + masks[0] = True + if count > 1: + masks[1, 5:12, 0] = False + if count > 2: + masks[2, 18:23, :] = False + target_class = torch.as_tensor(train.y_cls[:count], dtype=torch.long, device=device) + target_intensity = torch.as_tensor(train.y_reg[:count], dtype=torch.float32, device=device) + reports: dict[str, Any] = {} + models: dict[str, nn.Module] = { + EARLYCONCAT: AlignedFusionModel("concat", dims=dims).to(device), + MOFE7_MLP: MixtureOfFusionExperts(dims=dims, **MODEL_CONFIG).to(device), + } + for name, model in models.items(): + model.train() + result = model(xs, masks) + loss = _loss(result, target_class, target_intensity) + loss.backward() + gradient = sum(float(p.grad.detach().abs().sum().cpu()) for p in model.parameters() if p.grad is not None) + reports[name] = { + "logits_shape": list(result["logits"].shape), + "intensity_shape": list(result["intensity"].shape), + "finite_loss": bool(torch.isfinite(loss).item()), + "gradient_l1": gradient, + } + mofe_model = models[MOFE7_MLP] + mo = mofe_model(xs, masks) + active = mo["availability"].any(dim=-1) + alpha_sums = mo["alpha"].sum(dim=-1) + alpha_error = float((alpha_sums[active] - 1).abs().max().cpu()) if active.any() else 0.0 + unavailable_weights = float(mo["alpha"].masked_select(~mo["availability"]).abs().max().cpu()) if (~mo["availability"]).any() else 0.0 + expert_gradients = { + name: sum(float(parameter.grad.detach().abs().sum().cpu()) for parameter in expert.parameters() if parameter.grad is not None) + for name, expert in mofe_model.experts.items() + } + router_gradient = sum(float(parameter.grad.detach().abs().sum().cpu()) for parameter in mofe_model.router.parameters() if parameter.grad is not None) + if alpha_error > 1e-6 or unavailable_weights > 1e-8: + raise RuntimeError(f"MoFE routing mask invariant failed: sum_error={alpha_error}, unavailable={unavailable_weights}") + if not all(value > 0 for value in expert_gradients.values()) or router_gradient <= 0: + raise RuntimeError(f"MoFE expert/router gradients are incomplete: {expert_gradients}; router={router_gradient}") + report = { + "passed": all(item["finite_loss"] and item["gradient_l1"] > 0 for item in reports.values()), + "seed": seed, + "device": str(device), + "cuda_device": torch.cuda.get_device_name(0) if device.type == "cuda" else None, + "batch_size_checked": count, + "steps": train.steps, + "models": reports, + "mofe_experts": list(EXPERT_NAMES), + "mofe_alpha_shape": list(mo["alpha"].shape), + "mofe_max_weight_sum_error": alpha_error, + "mofe_max_weight_on_unavailable_experts": unavailable_weights, + "mofe_expert_gradient_l1": expert_gradients, + "mofe_router_gradient_l1": router_gradient, + "parameter_count": {row["method"]: row["trainable_parameters"] for row in _parameter_rows(dims, device)}, + } + output.mkdir(parents=True, exist_ok=True) + (output / "smoke_test.json").write_text(json.dumps(report, indent=2), encoding="utf-8") + return report + + +def _run(args: argparse.Namespace) -> None: + output = args.output_dir.resolve() + output.mkdir(parents=True, exist_ok=True) + device = _device_for(args.device) + torch.set_num_threads(args.threads) + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + + raw = load_aligned() + computed_stats = fit_robust_stats(raw["train"]) + reference_stats_path = REFERENCE_OUTPUT / "aligned_robust_stats.npz" + if reference_stats_path.exists(): + stats = RobustStats.load(reference_stats_path) + scaler_diff = max( + max(float(np.max(np.abs(a - b))) for a, b in zip(computed_stats.center, stats.center)), + max(float(np.max(np.abs(a - b))) for a, b in zip(computed_stats.scale, stats.scale)), + ) + else: + stats = computed_stats + scaler_diff = 0.0 + train = apply_robust_stats(raw["train"], stats) + valid = apply_robust_stats(raw["valid"], stats) + stats.save(output / "aligned_robust_stats.npz") + dims = tuple(int(x.shape[-1]) for x in train.x) + feature_path = ATTACHMENT2 / "aligned_50.pkl" + if not feature_path.exists(): + raise FileNotFoundError(f"official aligned feature file not found: {feature_path}") + + if args.phase == "smoke": + report = _smoke_test(train, output, device, args.seeds[0]) + report["scaler_max_abs_difference_from_reference"] = scaler_diff + (output / "smoke_test.json").write_text(json.dumps(report, indent=2), encoding="utf-8") + print(f"selected-model smoke: passed={report['passed']} device={device}", flush=True) + return + + seeds = list(args.seeds) + metrics_rows: list[dict[str, Any]] = [] + predictions: dict[tuple[str, int, str], dict[str, np.ndarray]] = {} + router_rows: list[dict[str, Any]] = [] + utility_rows: list[dict[str, Any]] = [] + expert_rows: list[dict[str, Any]] = [] + history_rows: list[dict[str, Any]] = [] + best_epochs: dict[str, int] = {} + condition_names: list[str] = [] + + for seed in seeds: + baseline_dir = output / "models" / "baselines" / "concat" / f"seed_{seed}" + baseline, baseline_epoch, baseline_history = _load_or_train_concat( + train, valid, baseline_dir, device, seed, args.epochs, args.patience, + args.batch_size, args.reuse_checkpoints and not args.force_retrain, + ) + mofe_dir = output / "models" / MOFE7_MLP / f"seed_{seed}" + mofe, mofe_epoch, mofe_history = _train_mofe( + train, valid, mofe_dir, device, seed, args.epochs, args.patience, + args.batch_size, args.reuse_checkpoints and not args.force_retrain, + ) + best_epochs[f"{EARLYCONCAT}_seed_{seed}"] = baseline_epoch + best_epochs[f"{MOFE7_MLP}_seed_{seed}"] = mofe_epoch + history_rows.extend(baseline_history) + history_rows.extend(mofe_history) + + conditions = _conditions(valid, seed) + names = [condition for condition, _, _ in conditions] + if condition_names and names != condition_names: + raise RuntimeError("validation condition ordering changed between seeds") + condition_names = names + for method, model in ((EARLYCONCAT, baseline), (MOFE7_MLP, mofe)): + for condition, rate, masks in conditions: + prediction = _predict(model, valid, masks, device, args.batch_size) + predictions[(method, seed, condition)] = prediction + metrics_rows.append(_condition_row(method, seed, condition, rate, valid, prediction)) + if method == MOFE7_MLP: + route_row, utility_row = _diagnostics(seed, condition, masks, prediction) + router_rows.append(route_row) + utility_rows.append(utility_row) + for expert in EXPERT_NAMES: + forced = _predict(model, valid, masks, device, args.batch_size, force_expert=expert) + observed = masks[..., list(SUBSETS[expert])].all(axis=-1) + expert_rows.append({ + "method": MOFE7_MLP, + "seed": seed, + "condition": condition, + "expert": expert, + "available_position_fraction": float(observed.mean()), + **_metric_dict(valid.y_cls, valid.y_reg, forced["logits"], forced["intensity"]), + }) + print(f"evaluated {method}/seed{seed}", flush=True) + del baseline, mofe + if torch.cuda.is_available(): + torch.cuda.empty_cache() + + summaries = _summary_rows(metrics_rows) + paired = _paired_bootstrap( + predictions, valid, seeds, condition_names, args.bootstrap_reps, + args.bootstrap_seed, summaries, + ) if args.bootstrap_reps > 0 else [] + parameter_rows = _parameter_rows(dims, device) + output_rows = { + "metrics_by_condition.csv": metrics_rows, + "summary.csv": summaries, + "paired_bootstrap.csv": paired, + "parameter_count.csv": parameter_rows, + "router_weights_by_condition.csv": router_rows, + "routing_entropy.csv": router_rows, + "modality_utility_by_condition.csv": utility_rows, + "expert_condition_matrix.csv": expert_rows, + "training_history.csv": history_rows, + } + for filename, rows in output_rows.items(): + _write_csv(output / filename, rows) + _plot_summary(output / "figures", summaries) + + manifest = { + "experiment": "Q2 selected models: EarlyConcat + BiGRU and MoFE-7 + MLP Router", + "created_unix": time.time(), + "python_version": sys.version, + "torch_version": torch.__version__, + "numpy_version": np.__version__, + "device": str(device), + "cuda_device": torch.cuda.get_device_name(0) if device.type == "cuda" else None, + "feature_file": str(feature_path), + "feature_sha256": _sha256(feature_path), + "feature_dimensions": dict(zip(MODALITIES, dims)), + "sequence_length": train.steps, + "representation_note": "official ordered 50-wordpiece positions; not 50 physical-time bins", + "train_examples": train.n, + "valid_examples": valid.n, + "train_source_video_groups": len({sample_id.split("$_$", 1)[0] for sample_id in train.ids}), + "valid_source_video_groups": len({sample_id.split("$_$", 1)[0] for sample_id in valid.ids}), + "train_only_scaler": str(output / "aligned_robust_stats.npz"), + "scaler_max_abs_difference_from_reference": scaler_diff, + "test_labels_used": False, + "seeds": seeds, + "epochs_max": args.epochs, + "patience": args.patience, + "batch_size": args.batch_size, + "optimizer": "AdamW(lr=1.5e-4, weight_decay=1e-4), gradient clip 1.0", + "training_mask_augmentation": "same contiguous-block augment_masks protocol for both models", + "validation_conditions": condition_names, + "validation_corruption_seed": "seed + 13 + pattern_index*101 + int(rate*1000)", + "loss": "cross_entropy + 0.5*SmoothL1(intensity/3, regression_label/3)", + "models": { + EARLYCONCAT: "project modalities independently, concatenate features and masks, then BiGRU", + MOFE7_MLP: { + "experts": list(EXPERT_NAMES), + "router": "MLP over per-position observed values and local observation statistics", + "availability": "hard mask; unavailable expert weights are zero", + "shared_temporal_backbone": "one BiGRU after position-wise expert mixture", + }, + }, + "best_epochs": best_epochs, + "paired_bootstrap": { + "replicates": args.bootstrap_reps, + "seed": args.bootstrap_seed, + "resampling_unit": "source video id", + "paired": True, + }, + } + (output / "run_manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") + print(f"selected-model results saved to {output}", flush=True) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Train and compare the two retained Q2 models.") + parser.add_argument("--phase", choices=("smoke", "full"), default="full") + parser.add_argument("--epochs", type=int, default=32) + parser.add_argument("--patience", type=int, default=6) + parser.add_argument("--batch-size", type=int, default=64) + parser.add_argument("--threads", type=int, default=4) + parser.add_argument("--device", default="auto") + parser.add_argument("--seeds", type=int, nargs="+", default=list(SEEDS)) + parser.add_argument("--bootstrap-reps", type=int, default=1000) + parser.add_argument("--bootstrap-seed", type=int, default=20260924) + parser.add_argument("--reuse-checkpoints", action="store_true") + parser.add_argument("--force-retrain", action="store_true") + parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT) + _run(parser.parse_args()) + + +if __name__ == "__main__": + main() diff --git a/submit/final/q2/math/__init__.py b/submit/final/q2/math/__init__.py new file mode 100644 index 0000000..442eaad --- /dev/null +++ b/submit/final/q2/math/__init__.py @@ -0,0 +1 @@ +"""Mathematical Q2 model family and training driver.""" diff --git a/submit/final/q2/math/data.py b/submit/final/q2/math/data.py new file mode 100644 index 0000000..61fe029 --- /dev/null +++ b/submit/final/q2/math/data.py @@ -0,0 +1,213 @@ +"""Restricted readers and split preparation for the official Q2 inputs.""" +from __future__ import annotations + +import pickle +from dataclasses import dataclass +from pathlib import Path +from typing import Any + +import numpy as np + +from ...data_paths import ATTACHMENT2, ATTACHMENT3, DATA_ROOT, PROJECT_ROOT + +ROOT = PROJECT_ROOT +ATTACHMENT2_DIR = ATTACHMENT2 +ALIGNED_PATH = ATTACHMENT2 / "aligned_50.pkl" +ATTACHMENT3_ALIGNED = ATTACHMENT3 / "对齐版本" +ATTACHMENT3_UNALIGNED = ATTACHMENT3 / "未对齐版本" +MODALITIES = ("text", "audio", "vision") +EXPECTED_DIMS = {"text": 768, "audio": 74, "vision": 35} + + +class RestrictedUnpickler(pickle.Unpickler): + """Allow only primitive containers and NumPy reconstruction primitives.""" + + _allowed = { + ("builtins", name): getattr(__import__("builtins"), name) + for name in ("set", "frozenset", "slice", "complex", "bytearray") + } + _allowed.update({ + ("collections", "OrderedDict"): __import__("collections").OrderedDict, + ("numpy", "ndarray"): np.ndarray, + ("numpy", "dtype"): np.dtype, + ("numpy", "asarray"): np.asarray, + ("numpy.core.multiarray", "_reconstruct"): np.core.multiarray._reconstruct, + ("numpy.core.multiarray", "scalar"): np.core.multiarray.scalar, + ("numpy._core.multiarray", "_reconstruct"): np.core.multiarray._reconstruct, + ("numpy._core.multiarray", "scalar"): np.core.multiarray.scalar, + }) + if hasattr(np.core.numeric, "_frombuffer"): + _allowed[("numpy.core.numeric", "_frombuffer")] = np.core.numeric._frombuffer + _allowed[("numpy._core.numeric", "_frombuffer")] = np.core.numeric._frombuffer + + def find_class(self, module: str, name: str) -> Any: + try: + return self._allowed[(module, name)] + except KeyError as exc: + raise pickle.UnpicklingError(f"blocked pickle global: {module}.{name}") from exc + + +def restricted_load(path: Path) -> Any: + with path.open("rb") as stream: + return RestrictedUnpickler(stream).load() + + +def _decode(value: Any) -> str: + if isinstance(value, bytes): + return value.decode("utf-8", errors="replace") + if isinstance(value, np.bytes_): + return bytes(value).decode("utf-8", errors="replace") + if isinstance(value, np.ndarray) and value.shape == (): + return _decode(value.item()) + return str(value) + + +def _one_dim(value: Any, dtype: Any | None = None) -> np.ndarray: + out = np.asarray(value) + if out.ndim > 1 and out.shape[-1] == 1: + out = out.reshape(-1) + elif out.ndim > 1 and out.shape[0] == 1: + out = out.reshape(-1) + else: + out = out.reshape(-1) + return out.astype(dtype) if dtype is not None else out + + +@dataclass +class SplitData: + name: str + x: dict[str, np.ndarray] + mask: np.ndarray + class_y: np.ndarray | None + regression_y: np.ndarray | None + ids: list[str] + groups: np.ndarray + alignment_audit: dict[str, Any] | None = None + + @property + def n(self) -> int: + return len(self.ids) + + +def _extract_split( + name: str, obj: dict[str, Any], with_labels: bool, + mask_override: np.ndarray | None = None, + alignment_audit: dict[str, Any] | None = None, +) -> SplitData: + raw: dict[str, np.ndarray] = {} + masks = [] + for modality in MODALITIES: + arr = np.asarray(obj[modality]) + if arr.ndim != 3 or arr.shape[1] != 50 or arr.shape[2] != EXPECTED_DIMS[modality]: + raise ValueError(f"{name}.{modality}: unexpected feature shape {arr.shape}") + arr = arr.astype(np.float32) + if not np.isfinite(arr).all(): + raise ValueError(f"{name}.{modality}: non-finite feature values; refusing to reinterpret them as missing") + # The dataset documentation defines all-zero aligned rows as missing. + observed = np.any(arr != 0.0, axis=-1) + raw[modality] = arr + masks.append(observed) + mask = np.stack(masks, axis=-1) + if mask_override is not None: + override = np.asarray(mask_override, bool) + if override.shape != mask.shape: + raise ValueError(f"{name}: projected mask shape {override.shape} differs from {mask.shape}") + mask = override + ids = [_decode(v) for v in _one_dim(obj["id"])] + if len(ids) != len(mask): + raise ValueError(f"{name}: id count differs from feature count") + if len(set(ids)) != len(ids): + raise ValueError(f"{name}: duplicate video$_$clip primary keys") + malformed = [sample_id for sample_id in ids if "$_$" not in sample_id or not all(sample_id.split("$_$", 1))] + if malformed: + raise ValueError(f"{name}: malformed video$_$clip keys: {malformed[:5]}") + groups = np.asarray([sample_group(v) for v in ids], dtype=str) + if with_labels: + class_y = _one_dim(obj["classification_labels"], np.int64) + regression_y = _one_dim(obj["regression_labels"], np.float32) + if len(class_y) != len(ids) or len(regression_y) != len(ids): + raise ValueError(f"{name}: label count differs from feature count") + if not np.isfinite(regression_y).all() or np.any(np.abs(regression_y) > 3.0): + raise ValueError(f"{name}: regression labels must be finite and within [-3,3]") + if not np.isin(class_y, [0, 1, 2]).all(): + raise ValueError(f"{name}: expected class labels in 0,1,2") + expected_class = np.where(regression_y < 0.0, 0, np.where(regression_y == 0.0, 1, 2)) + mismatch = np.flatnonzero(class_y != expected_class) + if len(mismatch): + examples = [(ids[int(i)], int(class_y[i]), float(regression_y[i])) for i in mismatch[:5]] + raise ValueError(f"{name}: polarity/regression label mismatch (sample, class, score): {examples}") + else: + class_y = regression_y = None + return SplitData(name, raw, mask, class_y, regression_y, ids, groups, alignment_audit) + + +def sample_group(sample_id: str) -> str: + """Official ids are video$_$clip; group on the source video only.""" + return sample_id.split("$_$", 1)[0] + + +def load_official_splits(path: Path = ALIGNED_PATH, *, version: str = "aligned_50") -> dict[str, SplitData]: + if version not in {"aligned_50", "unaligned_50"}: + raise ValueError(f"unsupported feature version: {version}") + obj = restricted_load(path) + required = {"train", "valid", "test"} + if not isinstance(obj, dict) or not required.issubset(obj): + raise ValueError(f"{path.name} must contain train, valid, and test dictionaries") + if version == "unaligned_50": + from ...adapter import adapt_official_split + + splits = {} + for name in ("train", "valid", "test"): + projected, mask, audit = adapt_official_split(obj[name]) + fields = {**obj[name], **projected} + splits[name] = _extract_split(name, fields, with_labels=True, + mask_override=mask, alignment_audit=audit) + else: + splits = {name: _extract_split(name, obj[name], with_labels=True) for name in ("train", "valid", "test")} + del obj + return splits + + +def load_attachment3_case(path: Path) -> dict[str, np.ndarray]: + obj = restricted_load(path) + case = obj.get("test", obj) + text_bert = np.asarray(case["text_bert"]) + audio = np.asarray(case["audio"]) + vision = np.asarray(case["vision"]) + if text_bert.ndim == 3 and text_bert.shape[0] == 1: + text_bert = text_bert[0] + if text_bert.shape != (3, 50): + raise ValueError(f"{path.name}: expected text_bert (1,3,50), got {np.asarray(case['text_bert']).shape}") + result = {"input_ids": text_bert[0].astype(np.int64), "attention_mask": text_bert[1].astype(bool), "token_type_ids": text_bert[2].astype(np.int64)} + for name, arr, dim in (("audio", audio, 74), ("vision", vision, 35)): + if arr.ndim == 3 and arr.shape[0] == 1: + arr = arr[0] + if arr.shape != (50, dim): + raise ValueError(f"{path.name}: expected {name} (1,50,{dim}), got {np.asarray(case[name]).shape}") + arr = arr.astype(np.float32) + if not np.isfinite(arr).all(): + raise ValueError(f"{path.name}: {name} contains non-finite features") + result[name] = arr + return result + + +def fit_preprocessor(train: SplitData) -> dict[str, dict[str, np.ndarray]]: + """Fit per-dimension mean/std on observed training rows only.""" + fitted: dict[str, dict[str, np.ndarray]] = {} + for j, name in enumerate(MODALITIES): + rows = train.x[name][train.mask[:, :, j]] + mean = rows.mean(axis=0, dtype=np.float64).astype(np.float32) + std = rows.std(axis=0, dtype=np.float64).astype(np.float32) + std[std < 1e-5] = 1.0 + fitted[name] = {"mean": mean, "std": std} + return fitted + + +def transform_split(split: SplitData, fitted: dict[str, dict[str, np.ndarray]]) -> dict[str, np.ndarray]: + output = {} + for j, name in enumerate(MODALITIES): + arr = (split.x[name] - fitted[name]["mean"]) / fitted[name]["std"] + arr = np.clip(arr, -10.0, 10.0) + arr[~split.mask[:, :, j]] = 0.0 + output[name] = arr.astype(np.float32) + return output diff --git a/submit/final/q2/math/predict_attachment3.py b/submit/final/q2/math/predict_attachment3.py new file mode 100644 index 0000000..27beb0d --- /dev/null +++ b/submit/final/q2/math/predict_attachment3.py @@ -0,0 +1,99 @@ +"""Run a saved Q2 model on the unlabeled Attachment 3 cases.""" +from __future__ import annotations + +import argparse +import json +import time +from pathlib import Path + +import numpy as np +import torch + +from ...data_paths import PROJECT_ROOT +from ...model.crg import INPUT_DIMS, MODALITIES, StructuredGaussianImputer +from .train import ( + _make_variant, + infer_attachment3, + reencode_attachment3, + validate_attachment3_predictions, + write_csv, +) + +DEFAULT_RESULTS_DIR = PROJECT_ROOT / "experiments" / "q2" / "unaligned_math_all_b128" +DEFAULT_OUTPUT_DIR = PROJECT_ROOT / "output" / "q2" + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--input-version", choices=("aligned_50", "unaligned_50"), default="unaligned_50") + parser.add_argument("--results-dir", type=Path, default=DEFAULT_RESULTS_DIR, + help="saved Q2 checkpoint and calibration directory") + parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT_DIR) + parser.add_argument("--device", choices=("auto", "cpu", "cuda"), default="auto") + args = parser.parse_args() + + if args.device == "cuda" and not torch.cuda.is_available(): + parser.error("CUDA was requested but is not available") + device_name = "cuda" if args.device == "auto" and torch.cuda.is_available() else args.device + if device_name == "auto": + device_name = "cpu" + device = torch.device(device_name) + + results_dir = args.results_dir.expanduser().resolve() + output_dir = args.output_dir.expanduser().resolve() + manifest_path = results_dir / "run_manifest.json" + calibration_path = results_dir / "validation_metrics.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + calibration = json.loads(calibration_path.read_text(encoding="utf-8")) + selected = calibration.get("selected_model", manifest.get("selected_model")) + if not selected: + raise ValueError(f"no selected_model recorded in {calibration_path}") + + imputer = StructuredGaussianImputer(INPUT_DIMS).to(device) + imputer.load_state_dict(torch.load(results_dir / "structured_imputer.pt", map_location=device, weights_only=True)) + model = _make_variant(selected, imputer).to(device) + model.load_state_dict(torch.load(results_dir / "crg_student.pt", map_location=device, weights_only=True)) + + with np.load(results_dir / "preprocessor.npz", allow_pickle=False) as archive: + fitted = {m: {k: archive[f"{m}_{k}"].copy() for k in ("mean", "std")} for m in MODALITIES} + priors = manifest["attachment3_low_information_priors"] + temperature = float(calibration["temperature"]) + class_prior = np.asarray(priors["class_probability_values"], dtype=np.float64) + magnitude_priors = np.asarray((priors["negative_beta"], priors["positive_beta"]), dtype=np.float32) + + cases, source_audit = reencode_attachment3(device, input_version=args.input_version) + predictions, inference_audit = infer_attachment3( + model, cases, fitted, device, temperature, class_prior, magnitude_priors, + ) + validate_attachment3_predictions([case["case_id"] for case in cases], predictions) + + output_dir.mkdir(parents=True, exist_ok=True) + inference_by_id = {row["case_id"]: row for row in inference_audit} + predictions_path = output_dir / "attachment3_predictions.csv" + audit_path = output_dir / "attachment3_audit.csv" + manifest_out_path = output_dir / "attachment3_prediction_manifest.json" + write_csv(predictions_path, predictions) + write_csv(audit_path, [{**source, **inference_by_id[source["case_id"]]} for source in source_audit]) + + try: + results_reference = results_dir.relative_to(PROJECT_ROOT).as_posix() + except ValueError: + results_reference = "external checkpoint directory" + prediction_manifest = { + "task": "unlabeled Attachment 3 inference", + "input_version": args.input_version, + "selected_model": selected, + "checkpoint_run": results_reference, + "prediction_count": len(predictions), + "temperature": temperature, + "labels_available": False, + "prediction_file": predictions_path.name, + "audit_file": audit_path.name, + "completed_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + } + manifest_out_path.write_text(json.dumps(prediction_manifest, ensure_ascii=False, indent=2), encoding="utf-8") + print(f"Wrote {len(predictions)} unlabeled Attachment 3 predictions to {output_dir}", flush=True) + + +if __name__ == "__main__": + main() diff --git a/submit/final/q2/math/train.py b/submit/final/q2/math/train.py new file mode 100644 index 0000000..6752a27 --- /dev/null +++ b/submit/final/q2/math/train.py @@ -0,0 +1,2131 @@ +"""Train/evaluate the Q2 model under the V2 official split and ablation design.""" +from __future__ import annotations + +import argparse +import copy +import csv +from contextlib import contextmanager +import hashlib +import json +import math +import random +import time +from pathlib import Path +from typing import Any + +import numpy as np +import torch +from scipy.optimize import minimize_scalar +from scipy.special import betainc, betaincinv +from scipy.stats import pearsonr +from sklearn.linear_model import LogisticRegression, Ridge +from sklearn.metrics import accuracy_score, f1_score, mean_absolute_error, mean_squared_error, recall_score +from sklearn.model_selection import GroupShuffleSplit +from torch.nn import functional as F +from transformers import AutoModel, AutoTokenizer + +from ...model.crg import CRG, INPUT_DIMS, MODALITIES, StructuredGaussianImputer +from .data import ( + ATTACHMENT2_DIR, + ATTACHMENT3_ALIGNED, + ATTACHMENT3_UNALIGNED, + DATA_ROOT, + ROOT, + SplitData, + fit_preprocessor, + load_attachment3_case, + load_official_splits, + transform_split, +) + +Q2_DIR = Path(__file__).resolve().parent +RESULTS = Q2_DIR / "results" +TEXT_MODEL_ID = "google-bert/bert-base-uncased" +SEED = 20260924 +MASK_RATES = (0.0, 0.1, 0.3, 0.5, 0.7) +MASK_MODES = ("single", "sync", "partial", "async") +DELTA_U = 0.01 +DISTILL_TEMPERATURE = 2.0 +LAMBDA_Y = 1.0 +LAMBDA_DISTILL = 0.1 +LAMBDA_RECON = 0.05 +LAMBDA_GROUP = 0.1 +GROUP_TEMPERATURE = 0.1 +GROUP_RISK_CANDIDATES = ((0.05, 0.1), (0.1, 0.05), (0.1, 0.1), (0.1, 0.2), (0.2, 0.1)) +LAMBDA_EMISSION = 1e-4 +LAMBDA_TRANSITION = 1e-4 +DEFAULT_RELIABILITY = (0.5, 0.05, 0.05, 0.05) +RELIABILITY_CANDIDATES = ( + (0.5, 0.0, 0.0, 0.0), + (0.5, 0.05, 0.05, 0.05), + (0.5, 0.1, 0.0, 0.0), + (0.3, 0.05, 0.05, 0.05), + (0.7, 0.05, 0.05, 0.05), +) + + +def seed_everything(seed: int) -> None: + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + torch.cuda.manual_seed_all(seed) + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + + +@contextmanager +def fixed_torch_seed(seed: int, device: torch.device): + devices = [device.index if device.index is not None else torch.cuda.current_device()] if device.type == "cuda" else [] + with torch.random.fork_rng(devices=devices): + torch.manual_seed(int(seed)) + if device.type == "cuda": + torch.cuda.manual_seed_all(int(seed)) + yield + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def label_resolution_from_train(labels: np.ndarray) -> float: + """Use half the smallest positive nonzero magnitude spacing in fit labels.""" + magnitudes = np.unique(np.round(np.abs(np.asarray(labels, dtype=np.float64)) / 3.0, 6)) + spacing = np.diff(magnitudes) + spacing = spacing[spacing > 1e-5] + return float(np.clip(0.5 * spacing.min(), 1e-4, 0.1)) if len(spacing) else 0.01 + + +def fit_magnitude_priors(labels: np.ndarray) -> np.ndarray: + """Method-of-moments sign-specific Beta priors from train labels only.""" + result = np.zeros((2, 2), dtype=np.float64) + for slot, selected in enumerate((np.asarray(labels) < 0, np.asarray(labels) > 0)): + values = np.abs(np.asarray(labels, dtype=np.float64)[selected]) / 3.0 + values = np.clip(values, 1e-4, 1.0 - 1e-4) + if len(values) < 2: + mean, concentration = 0.5, 4.0 + else: + mean, variance = float(values.mean()), float(values.var(ddof=1)) + concentration = mean * (1.0 - mean) / max(variance, 1e-5) - 1.0 + concentration = float(np.clip(concentration, 2.0, 100.0)) + result[slot] = (max(1e-3, mean * concentration), max(1e-3, (1.0 - mean) * concentration)) + return result.astype(np.float32) + + +def assert_group_disjoint(splits: dict[str, SplitData]) -> dict[str, int]: + overlap: dict[str, int] = {} + for left, right in (("train", "valid"), ("train", "test"), ("valid", "test")): + shared = set(splits[left].groups) & set(splits[right].groups) + overlap[f"{left}_{right}"] = len(shared) + if shared: + raise ValueError(f"source-video leakage across {left}/{right}: {sorted(shared)[:5]}") + return overlap + + +def select_rows(split: SplitData, indices: np.ndarray, name: str) -> SplitData: + idx = np.asarray(indices, dtype=np.int64) + return SplitData( + name=name, + x={m: split.x[m][idx].copy() for m in MODALITIES}, + mask=split.mask[idx].copy(), + class_y=split.class_y[idx].copy() if split.class_y is not None else None, + regression_y=split.regression_y[idx].copy() if split.regression_y is not None else None, + ids=[split.ids[int(i)] for i in idx], + groups=split.groups[idx].copy(), + ) + + +def split_calibration(train: SplitData, seed: int, fraction: float = 0.1) -> tuple[SplitData, SplitData]: + splitter = GroupShuffleSplit(n_splits=1, test_size=fraction, random_state=seed) + fit_idx, cal_idx = next(splitter.split(np.zeros(train.n), train.class_y, train.groups)) + fit, cal = select_rows(train, fit_idx, "fit"), select_rows(train, cal_idx, "calibration") + if set(fit.groups) & set(cal.groups): + raise AssertionError("internal fit/calibration source videos overlap") + return fit, cal + + +def _best_interval( + visible: np.ndarray, + wanted: int, + cap: int, + location: str, + rng: np.random.Generator, +) -> tuple[int, int] | None: + steps = len(visible) + candidates: list[tuple[int, int, int, int]] = [] + for left in range(steps): + hits = 0 + for right in range(left, steps): + hits += int(visible[right]) + count = min(hits, cap) + if count: + candidates.append((abs(count - wanted), right - left + 1, left, right)) + if not candidates: + return None + best = min((error, span) for error, span, _, _ in candidates) + tied = [(left, right) for error, span, left, right in candidates if (error, span) == best] + if location == "start": + return min(tied, key=lambda pair: (pair[0], pair[1])) + if location == "end": + return max(tied, key=lambda pair: (pair[1], pair[0])) + if location == "middle": + center = (steps - 1) / 2 + return min(tied, key=lambda pair: (abs((pair[0] + pair[1]) / 2 - center), pair[0])) + if location != "random": + raise ValueError(f"unknown interval location: {location}") + return tied[int(rng.integers(0, len(tied)))] + + +def _spread_short_spans(visible: np.ndarray, wanted: int, cap: int) -> np.ndarray: + positions = np.flatnonzero(visible) + count = min(int(wanted), int(cap), len(positions)) + chosen = np.zeros(len(visible), dtype=bool) + if count <= 0: + return chosen + n_spans = min(3, count) + chunks = np.array_split(positions, n_spans) + allocations = [count // n_spans + int(i < count % n_spans) for i in range(n_spans)] + for chunk, amount in zip(chunks, allocations): + if amount <= 0 or len(chunk) == 0: + continue + amount = min(amount, len(chunk)) + start = max(0, (len(chunk) - amount) // 2) + chosen[chunk[start:start + amount]] = True + return chosen + + +def continuous_mask( + original: np.ndarray, + rate: float, + mode: str, + rng: np.random.Generator, + *, + modalities: tuple[int, ...] | None = None, + location: str = "random", + span_structure: str = "long", + kind: str = "continuous", +) -> np.ndarray: + """Hide contiguous feature rows while preserving at least 20% per selected source. + + ``sync``, ``partial`` and ``async`` use a shared, shifted-overlap, or + staggered span layout. Evaluation controls may pin the affected modalities, + gap location, and one-long versus several-short structure. + """ + observed = np.asarray(original, dtype=bool) + result = observed.copy() + if rate <= 0 or mode == "none": + return result + steps, modality_count = observed.shape + present = [m for m in range(modality_count) if observed[:, m].any()] + if not present: + return result + if modalities is not None: + selected = [int(m) for m in modalities if int(m) in present] + if not selected: + return result + elif mode == "single": + selected = [int(rng.choice(present))] + elif mode in {"sync", "partial", "async"}: + if len(present) == 1: + selected = present + else: + count = int(rng.integers(2, min(3, len(present)) + 1)) + selected = sorted(int(v) for v in rng.choice(present, size=count, replace=False)) + else: + raise ValueError(f"unknown mask mode: {mode}") + + def max_hide(modality: int) -> int: + count = int(observed[:, modality].sum()) + keep = max(1, int(math.ceil(0.2 * count))) + return max(0, count - keep) + + target = {m: min(max_hide(m), int(round(rate * int(observed[:, m].sum())))) for m in selected} + if kind == "point": + for modality in selected: + candidates = np.flatnonzero(observed[:, modality]) + count = target[modality] + if count > 0: + hidden = rng.choice(candidates, size=count, replace=False) + result[hidden, modality] = False + return result + if kind != "continuous": + raise ValueError(f"unknown mask kind: {kind}") + if mode == "sync": + span = max(1, int(round(rate * steps))) + if location == "start": + left = 0 + elif location == "end": + left = steps - span + elif location == "middle": + left = (steps - span) // 2 + elif location == "random": + left = int(rng.integers(0, max(1, steps - span + 1))) + else: + raise ValueError(f"unknown interval location: {location}") + right = min(steps - 1, left + span - 1) + for m in selected: + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + amount = min(len(candidates), max_hide(m), target[m]) + if amount: + offset = 0 if location != "end" else len(candidates) - amount + candidates = candidates[max(0, offset):max(0, offset) + amount] + result[candidates, m] = False + else: + common_span = max(1, int(round(rate * steps))) + for rank, m in enumerate(selected): + wanted = target[m] + if wanted <= 0: + continue + cap = max_hide(m) + if span_structure == "multi_short": + hide = _spread_short_spans(observed[:, m], wanted, cap) + elif span_structure != "long": + raise ValueError(f"unknown span structure: {span_structure}") + elif mode == "single" and location != "random": + span = max(1, int(round(rate * steps))) + if location == "start": + left = 0 + elif location == "end": + left = steps - span + elif location == "middle": + left = (steps - span) // 2 + else: + raise ValueError(f"unknown interval location: {location}") + right = min(steps - 1, left + span - 1) + hide = np.zeros(steps, dtype=bool) + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + hide[candidates[:cap]] = True + elif mode in {"partial", "async"}: + if mode == "partial": + base_left = int(rng.integers(0, max(1, steps - common_span + 1))) if location == "random" else ( + 0 if location == "start" else steps - common_span if location == "end" else (steps - common_span) // 2 + ) + offset = int(round(rank * common_span * 0.5)) + else: + base_left = 0 if location == "random" else ( + 0 if location == "start" else steps - common_span if location == "end" else (steps - common_span) // 2 + ) + available = max(1, steps - common_span + 1) + offsets = np.rint(np.linspace(0, max(0, available - 1), len(selected))).astype(int) + if location == "random": + rng.shuffle(offsets) + offset = int(offsets[rank]) + left = min(max(0, base_left + offset), max(0, steps - common_span)) + right = min(steps - 1, left + common_span - 1) + hide = np.zeros(steps, dtype=bool) + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + amount = min(len(candidates), wanted, cap) + if amount: + hide[candidates[:amount]] = True + else: + interval = _best_interval(observed[:, m], wanted, cap, location, rng) + hide = np.zeros(steps, dtype=bool) + if interval is not None: + left, right = interval + candidates = np.flatnonzero(observed[left:right + 1, m]) + left + amount = min(len(candidates), wanted, cap) + if amount: + offset = 0 if location != "end" else len(candidates) - amount + hide[candidates[max(0, offset):max(0, offset) + amount]] = True + result[hide, m] = False + return result + + +def make_scenarios(split: SplitData, seed: int) -> dict[str, np.ndarray]: + scenarios = {"0.0/none": split.mask.copy()} + for rate in MASK_RATES[1:]: + for mode in MASK_MODES: + key = f"{rate:.1f}/{mode}" + masks = [] + for sample_id, original in zip(split.ids, split.mask): + sample_seed = int.from_bytes(hashlib.sha256(f"{seed}:{sample_id}:{key}".encode()).digest()[:8], "little") + masks.append(continuous_mask(original, rate, mode, np.random.default_rng(sample_seed))) + scenarios[key] = np.stack(masks) + + modality_sets = ( + ((0,), "T"), ((1,), "A"), ((2,), "V"), + ((0, 1), "TA"), ((0, 2), "TV"), ((1, 2), "AV"), ((0, 1, 2), "TAV"), + ) + for selected, label in modality_sets: + key = f"0.3/modality_{label}" + scenarios[key] = np.stack([ + continuous_mask( + original, 0.3, "sync", np.random.default_rng(_scenario_seed(seed, sample_id, key)), + modalities=selected, + ) + for sample_id, original in zip(split.ids, split.mask) + ]) + + for modality_index, label in enumerate(("T", "A", "V")): + for location in ("start", "middle", "end"): + key = f"0.3/location_{location}_{label}" + scenarios[key] = np.stack([ + continuous_mask( + original, 0.3, "single", np.random.default_rng(_scenario_seed(seed, sample_id, key)), + modalities=(modality_index,), location=location, + ) + for sample_id, original in zip(split.ids, split.mask) + ]) + for structure in ("long", "multi_short"): + key = f"0.3/span_{structure}_{label}" + scenarios[key] = np.stack([ + continuous_mask( + original, 0.3, "single", np.random.default_rng(_scenario_seed(seed, sample_id, key)), + modalities=(modality_index,), span_structure=structure, + ) + for sample_id, original in zip(split.ids, split.mask) + ]) + + for mode in ("sync", "partial", "async"): + key = f"0.3/synchrony_{mode}" + scenarios[key] = np.stack([ + continuous_mask( + original, 0.3, mode, np.random.default_rng(_scenario_seed(seed, sample_id, key)), + modalities=(0, 1, 2), + ) + for sample_id, original in zip(split.ids, split.mask) + ]) + return scenarios + + +def _scenario_seed(seed: int, sample_id: str, key: str) -> int: + return int.from_bytes(hashlib.sha256(f"{seed}:{sample_id}:{key}".encode()).digest()[:8], "little") + + +def make_reliability_scenarios(split: SplitData, seed: int) -> dict[str, np.ndarray]: + scenarios = {"0.0/natural": split.mask.copy()} + for rate, mode in ((0.3, "single"), (0.3, "sync"), (0.5, "async")): + key = f"{rate:.1f}/{mode}" + scenarios[key] = np.stack([ + continuous_mask(original, rate, mode, np.random.default_rng(_scenario_seed(seed, sample_id, key))) + for sample_id, original in zip(split.ids, split.mask) + ]) + return scenarios + + +def to_device_batch( + arrays: dict[str, np.ndarray], masks: np.ndarray, indices: np.ndarray, device: torch.device, +) -> tuple[list[torch.Tensor], torch.Tensor]: + idx = np.asarray(indices, dtype=np.int64) + xs = [torch.from_numpy(arrays[m][idx]).to(device=device, dtype=torch.float32) for m in MODALITIES] + observed = torch.from_numpy(masks[idx].astype(bool)).to(device=device) + return xs, observed + + +def _betacf(a: torch.Tensor, b: torch.Tensor, x: torch.Tensor, iterations: int = 64) -> torch.Tensor: + """Differentiable continued fraction for the regularized incomplete beta.""" + tiny = 1e-12 + qab, qap, qam = a + b, a + 1.0, a - 1.0 + c = torch.ones_like(x) + d = 1.0 - qab * x / qap + d = 1.0 / torch.where(d.abs() < tiny, torch.full_like(d, tiny), d) + h = d + for m in range(1, iterations + 1): + mf = float(m) + aa = mf * (b - mf) * x / ((qam + 2.0 * mf) * (a + 2.0 * mf)) + d = 1.0 + aa * d + d = 1.0 / torch.where(d.abs() < tiny, torch.full_like(d, tiny), d) + c = 1.0 + aa / torch.where(c.abs() < tiny, torch.full_like(c, tiny), c) + h = h * d * c + aa = -(a + mf) * (qab + mf) * x / ((a + 2.0 * mf) * (qap + 2.0 * mf)) + d = 1.0 + aa * d + d = 1.0 / torch.where(d.abs() < tiny, torch.full_like(d, tiny), d) + c = 1.0 + aa / torch.where(c.abs() < tiny, torch.full_like(c, tiny), c) + h = h * d * c + return h + + +def regularized_beta(x: torch.Tensor, a: torch.Tensor, b: torch.Tensor) -> torch.Tensor: + x_full, a_full, b_full = torch.broadcast_tensors(x, a, b) + safe_x = x_full.clamp(1e-7, 1.0 - 1e-7) + log_bt = torch.lgamma(a_full + b_full) - torch.lgamma(a_full) - torch.lgamma(b_full) + log_bt = log_bt + a_full * torch.log(safe_x) + b_full * torch.log1p(-safe_x) + bt = torch.exp(log_bt.clamp(-80.0, 30.0)) + lower = safe_x < (a_full + 1.0) / (a_full + b_full + 2.0) + direct = bt * _betacf(a_full, b_full, safe_x) / a_full + complement = 1.0 - bt * _betacf(b_full, a_full, 1.0 - safe_x) / b_full + result = torch.where(lower, direct, complement).clamp(0.0, 1.0) + return torch.where(x_full <= 0.0, torch.zeros_like(result), torch.where(x_full >= 1.0, torch.ones_like(result), result)) + + +def supervised_loss_per_sample( + output: dict[str, Any], class_y: torch.Tensor, regression_y: torch.Tensor, +) -> tuple[torch.Tensor, dict[str, torch.Tensor]]: + probs = output["class_probs_by_path"].clamp_min(1e-8) + beta = output["beta_params"].clamp_min(1e-4) + path_count, batch = probs.shape[:2] + negative = class_y == 0 + neutral = class_y == 1 + positive = class_y == 2 + u = (regression_y.abs() / 3.0).clamp(0.0, 1.0) + lo = (u - DELTA_U).clamp(0.0, 1.0) + hi = (u + DELTA_U).clamp(0.0, 1.0) + neutral_mass = probs[:, :, 1] + neg_params = beta[:, :, 0, :] + pos_params = beta[:, :, 1, :] + cdf_hi_neg = regularized_beta(hi.unsqueeze(0), neg_params[..., 0], neg_params[..., 1]) + cdf_lo_neg = regularized_beta(lo.unsqueeze(0), neg_params[..., 0], neg_params[..., 1]) + cdf_hi_pos = regularized_beta(hi.unsqueeze(0), pos_params[..., 0], pos_params[..., 1]) + cdf_lo_pos = regularized_beta(lo.unsqueeze(0), pos_params[..., 0], pos_params[..., 1]) + neg_mass = probs[:, :, 0] * (cdf_hi_neg - cdf_lo_neg).clamp_min(1e-12) + pos_mass = probs[:, :, 2] * (cdf_hi_pos - cdf_lo_pos).clamp_min(1e-12) + selected = torch.where(neutral.unsqueeze(0), neutral_mass, torch.where(negative.unsqueeze(0), neg_mass, pos_mass)) + mixture_mass = selected.mean(dim=0).clamp_min(1e-12) + nll = -torch.log(mixture_mass) + beta_mean = beta[..., 0] / beta.sum(dim=-1) + conditional = 3.0 * (probs[:, :, 2] * beta_mean[:, :, 1] - probs[:, :, 0] * beta_mean[:, :, 0]) + mean_score = conditional.mean(dim=0) + scaled_error = (regression_y - mean_score) / 3.0 + huber = F.huber_loss(scaled_error, torch.zeros_like(scaled_error), reduction="none", delta=0.25) + total = nll + LAMBDA_Y * huber + return total, {"nll": nll, "huber": huber, "mean_score": mean_score} + + +def reconstruction_loss_per_sample( + output: dict[str, Any], xs: list[torch.Tensor], hidden: torch.Tensor, +) -> torch.Tensor: + batch = hidden.shape[0] + per_modal = [] + for m, prediction in enumerate(output["reconstructions"]): + target = xs[m].unsqueeze(0) + error = (prediction - target).abs().mean(dim=-1) + mask = hidden[:, :, m].float().unsqueeze(0) + numerator = (error * mask).sum(dim=(0, 2)) + denominator = mask.sum(dim=(0, 2)).clamp_min(1.0) + per_modal.append(numerator / denominator) + values = torch.stack(per_modal, dim=-1) + active = torch.stack([hidden[:, :, m].any(dim=1) for m in range(len(MODALITIES))], dim=-1).float() + return (values * active).sum(dim=-1) / active.sum(dim=-1).clamp_min(1.0) + + +def _regularized_logits(probabilities: np.ndarray, temperature: float) -> np.ndarray: + logp = np.log(np.clip(probabilities, 1e-12, 1.0)) / temperature + logp -= logp.max(axis=1, keepdims=True) + exp = np.exp(logp) + return exp / exp.sum(axis=1, keepdims=True) + + +def _decode_mixture( + probabilities_by_path: np.ndarray, + beta_params: np.ndarray, + temperature: float = 1.0, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + pbar = _regularized_logits(probabilities_by_path.mean(axis=0), temperature) + maximum = pbar.max(axis=1, keepdims=True) + ties = np.isclose(pbar, maximum, rtol=0.0, atol=1e-12) + predicted_class = np.asarray([next(c for c in (1, 0, 2) if row[c]) for row in ties], dtype=np.int64) + score = np.zeros(len(predicted_class), dtype=np.float32) + for i, cls in enumerate(predicted_class): + if cls == 1: + continue + sign_index = 0 if cls == 0 else 1 + weights = probabilities_by_path[:, i, cls] + params = beta_params[:, i, sign_index] + denominator = float(weights.sum()) + if denominator <= 1e-12: + magnitude = 0.5 + else: + low, high = 0.0, 1.0 + for _ in range(48): + middle = (low + high) / 2.0 + cdf = float(np.dot(weights, betainc(params[:, 0], params[:, 1], middle)) / denominator) + if cdf < 0.5: + low = middle + else: + high = middle + magnitude = (low + high) / 2.0 + score[i] = (-3.0 if cls == 0 else 3.0) * magnitude + return pbar, predicted_class, score + + +def _predictive_intervals( + probabilities_by_path: np.ndarray, + beta_params: np.ndarray, + temperature: float, + quantiles: tuple[float, float] = (0.05, 0.95), +) -> tuple[np.ndarray, np.ndarray]: + """Central intervals of the calibrated signed point-mass/Beta mixture.""" + pbar = _regularized_logits(probabilities_by_path.mean(axis=0), temperature) + lower = np.empty(len(pbar), dtype=np.float32) + upper = np.empty(len(pbar), dtype=np.float32) + for i, marginal in enumerate(pbar): + negative_weight = probabilities_by_path[:, i, 0].astype(np.float64) + positive_weight = probabilities_by_path[:, i, 2].astype(np.float64) + negative_weight /= negative_weight.sum() + positive_weight /= positive_weight.sum() + negative = beta_params[:, i, 0].astype(np.float64) + positive = beta_params[:, i, 1].astype(np.float64) + + def cdf(value: float) -> float: + if value < 0.0: + magnitude_threshold = min(1.0, max(0.0, -value / 3.0)) + conditional = np.dot( + negative_weight, + 1.0 - betainc(negative[:, 0], negative[:, 1], magnitude_threshold), + ) + return float(marginal[0] * conditional) + magnitude_threshold = min(1.0, max(0.0, value / 3.0)) + conditional = np.dot( + positive_weight, + betainc(positive[:, 0], positive[:, 1], magnitude_threshold), + ) + return float(marginal[0] + marginal[1] + marginal[2] * conditional) + + for slot, quantile in enumerate(quantiles): + lo, hi = -3.0, 3.0 + for _ in range(52): + mid = (lo + hi) / 2.0 + if cdf(mid) >= quantile: + hi = mid + else: + lo = mid + if slot == 0: + lower[i] = hi + else: + upper[i] = hi + return lower, upper + + +def _trajectory_variance_components( + probabilities_by_path: np.ndarray, + beta_params: np.ndarray, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Verify Var(Y)=E_b Var(Y|b)+Var_b(E[Y|b]) before class calibration.""" + probs = np.asarray(probabilities_by_path, dtype=np.float64) + beta = np.asarray(beta_params, dtype=np.float64) + mean = beta[..., 0] / beta.sum(axis=-1) + second = beta[..., 0] * (beta[..., 0] + 1.0) / (beta.sum(axis=-1) * (beta.sum(axis=-1) + 1.0)) + path_mean = 3.0 * (probs[..., 2] * mean[..., 1] - probs[..., 0] * mean[..., 0]) + path_second = 9.0 * (probs[..., 2] * second[..., 1] + probs[..., 0] * second[..., 0]) + conditional_variance = np.maximum(0.0, path_second - np.square(path_mean)) + within = conditional_variance.mean(axis=0) + between = path_mean.var(axis=0) + total = within + between + return total.astype(np.float32), within.astype(np.float32), between.astype(np.float32) + + +def _calibrated_mixture_moments( + probabilities_by_path: np.ndarray, + beta_params: np.ndarray, + temperature: float, +) -> tuple[np.ndarray, np.ndarray]: + """Recompute mean and variance after temperature calibration of class mass.""" + pcal = _regularized_logits(probabilities_by_path.mean(axis=0), temperature) + beta = np.asarray(beta_params, dtype=np.float64) + raw_probs = np.asarray(probabilities_by_path, dtype=np.float64) + beta_mean = beta[..., 0] / beta.sum(axis=-1) + beta_second = beta[..., 0] * (beta[..., 0] + 1.0) / (beta.sum(axis=-1) * (beta.sum(axis=-1) + 1.0)) + conditional_mean = np.zeros((raw_probs.shape[1], 2), dtype=np.float64) + conditional_second = np.zeros((raw_probs.shape[1], 2), dtype=np.float64) + for sign_index, class_index in enumerate((0, 2)): + weights = raw_probs[:, :, class_index] + weights = weights / weights.sum(axis=0, keepdims=True) + conditional_mean[:, sign_index] = np.sum(weights * beta_mean[:, :, sign_index], axis=0) + conditional_second[:, sign_index] = np.sum(weights * beta_second[:, :, sign_index], axis=0) + mean = 3.0 * (pcal[:, 2] * conditional_mean[:, 1] - pcal[:, 0] * conditional_mean[:, 0]) + second = 9.0 * (pcal[:, 2] * conditional_second[:, 1] + pcal[:, 0] * conditional_second[:, 0]) + variance = np.maximum(0.0, second - np.square(mean)) + return mean.astype(np.float32), variance.astype(np.float32) + + +def calculate_metrics( + y_cls: np.ndarray, + y_reg: np.ndarray, + probs: np.ndarray, + pred_cls: np.ndarray, + pred_reg: np.ndarray, + selection_nll: float | None = None, + interval_lower: np.ndarray | None = None, + interval_upper: np.ndarray | None = None, + variance_components: tuple[np.ndarray, np.ndarray, np.ndarray] | None = None, + calibrated_moments: tuple[np.ndarray, np.ndarray] | None = None, +) -> dict[str, Any]: + p = np.clip(probs, 1e-8, 1.0) + onehot = np.eye(3, dtype=np.float64)[y_cls] + nll = float(-np.log(p[np.arange(len(y_cls)), y_cls]).mean()) + confidence = p.max(axis=1) + correct = (pred_cls == y_cls).astype(np.float64) + ece = 0.0 + for left in np.linspace(0, 1, 16)[:-1]: + right = left + 1 / 15 + selected = (confidence >= left) & (confidence < right if right < 1 else confidence <= right) + if selected.any(): + ece += selected.mean() * abs(confidence[selected].mean() - correct[selected].mean()) + pearson = float(pearsonr(y_reg, pred_reg).statistic) if np.std(y_reg) > 0 and np.std(pred_reg) > 0 else float("nan") + support = np.bincount(y_cls.astype(int), minlength=3) + result = { + "n": int(len(y_cls)), + "accuracy": float(accuracy_score(y_cls, pred_cls)), + "macro_f1": float(f1_score(y_cls, pred_cls, labels=[0, 1, 2], average="macro", zero_division=0)), + "negative_support": int(support[0]), + "neutral_support": int(support[1]), + "positive_support": int(support[2]), + "negative_recall": float(recall_score(y_cls, pred_cls, labels=[0], average="macro", zero_division=0)), + "middle_recall": float(recall_score(y_cls, pred_cls, labels=[1], average="macro", zero_division=0)), + "positive_recall": float(recall_score(y_cls, pred_cls, labels=[2], average="macro", zero_division=0)), + "regression_mae": float(mean_absolute_error(y_reg, pred_reg)), + "regression_rmse": float(np.sqrt(mean_squared_error(y_reg, pred_reg))), + "pearson": pearson, + "brier": float(np.square(probs - onehot).sum(axis=1).mean()), + "classification_nll": nll, + "ece_15": float(ece), + } + if selection_nll is not None: + result["selection_nll"] = float(selection_nll) + if interval_lower is not None and interval_upper is not None: + result["interval_90_coverage"] = float(np.mean((y_reg >= interval_lower) & (y_reg <= interval_upper))) + result["interval_90_mean_width"] = float(np.mean(interval_upper - interval_lower)) + if variance_components is not None: + total, within, between = variance_components + result["predictive_variance_mean_uncalibrated"] = float(np.mean(total)) + result["within_trajectory_variance_mean"] = float(np.mean(within)) + result["between_trajectory_variance_mean"] = float(np.mean(between)) + if calibrated_moments is not None: + calibrated_mean, calibrated_variance = calibrated_moments + result["predictive_mean_mean_calibrated"] = float(np.mean(calibrated_mean)) + result["predictive_variance_mean_calibrated"] = float(np.mean(calibrated_variance)) + return result + + +def evaluate( + model: CRG, + arrays: dict[str, np.ndarray], + split: SplitData, + device: torch.device, + batch_size: int, + *, + masks: np.ndarray | None = None, + temperature: float = 1.0, + collect_gate_diagnostics: bool = False, +) -> tuple[dict[str, Any], dict[str, np.ndarray]]: + model.eval() + source_masks = split.mask if masks is None else np.asarray(masks, dtype=bool) + prob_paths, beta_paths, loss_parts = [], [], [] + gate_parts: dict[str, list[np.ndarray]] = { + "fusion_weights": [], "null_weights": [], "time_pool_weights": [], "reliability": [], + "imputation_uncertainty": [], "gap": [], "span": [], "distance_before": [], "distance_after": [], + } + with torch.inference_mode(): + for start in range(0, split.n, batch_size): + idx = np.arange(start, min(split.n, start + batch_size)) + xs, observed = to_device_batch(arrays, source_masks, idx, device) + out = model(xs, observed, paths=16 if model.use_joint_draws else 1, joint_draws=model.use_joint_draws) + prob_paths.append(out["class_probs_by_path"].cpu().numpy()) + beta_paths.append(out["beta_params"].cpu().numpy()) + if collect_gate_diagnostics: + for name, key in (("fusion_weights", "fusion_weights_by_path"), + ("null_weights", "null_weights_by_path"), + ("time_pool_weights", "time_pool_weights_by_path")): + gate_parts[name].append(out[key].mean(dim=0).cpu().numpy()) + for name in ("reliability", "imputation_uncertainty", "gap", "span", "distance_before", "distance_after"): + gate_parts[name].append(out[name].cpu().numpy()) + cy = torch.from_numpy(split.class_y[idx]).to(device) + ry = torch.from_numpy(split.regression_y[idx]).to(device) + nll, _ = supervised_loss_per_sample(out, cy, ry) + loss_parts.append(nll.cpu().numpy()) + ppaths = np.concatenate(prob_paths, axis=1) + betas = np.concatenate(beta_paths, axis=1) + probs, pred_class, pred_score = _decode_mixture(ppaths, betas, temperature) + interval_lower, interval_upper = _predictive_intervals(ppaths, betas, temperature) + variance_components = _trajectory_variance_components(ppaths, betas) + calibrated_moments = _calibrated_mixture_moments(ppaths, betas, temperature) + metrics = calculate_metrics( + split.class_y, split.regression_y, probs, pred_class, pred_score, + float(np.mean(np.concatenate(loss_parts))), interval_lower, interval_upper, variance_components, + calibrated_moments, + ) + predictions = { + "probabilities": probs, + "probabilities_by_path": ppaths, + "beta_params": betas, + "predicted_class": pred_class, + "predicted_score": pred_score, + "interval_lower": interval_lower, + "interval_upper": interval_upper, + "predictive_variance_uncalibrated": variance_components[0], + "within_trajectory_variance": variance_components[1], + "between_trajectory_variance": variance_components[2], + "predictive_mean_calibrated": calibrated_moments[0], + "predictive_variance_calibrated": calibrated_moments[1], + } + if collect_gate_diagnostics: + predictions.update({name: np.concatenate(values, axis=0) for name, values in gate_parts.items()}) + return metrics, predictions + + +def gate_diagnostic_rows(split: SplitData, predictions: dict[str, np.ndarray]) -> list[dict[str, Any]]: + required = {"fusion_weights", "null_weights", "time_pool_weights", "reliability", + "imputation_uncertainty", "gap", "span", "distance_before", "distance_after"} + if not required.issubset(predictions): + raise ValueError(f"missing gate diagnostic arrays: {sorted(required - set(predictions))}") + rows = [] + for sample_index, sample_id in enumerate(split.ids): + for step in range(split.mask.shape[1]): + for modality_index, modality in enumerate(MODALITIES): + rows.append({ + "sample_id": sample_id, + "video_id": split.groups[sample_index], + "step": step, + "relative_position": step / max(1, split.mask.shape[1] - 1), + "modality": modality, + "observed": bool(split.mask[sample_index, step, modality_index]), + "fusion_weight_mean_over_paths": float(predictions["fusion_weights"][sample_index, step, modality_index]), + "null_weight_mean_over_paths": float(predictions["null_weights"][sample_index, step]), + "reliability": float(predictions["reliability"][sample_index, step, modality_index]), + "imputation_uncertainty": float(predictions["imputation_uncertainty"][sample_index, step, modality_index]), + "nearest_observation_gap": float(predictions["gap"][sample_index, step, modality_index]), + "continuous_missing_span": float(predictions["span"][sample_index, step, modality_index]), + "distance_before": float(predictions["distance_before"][sample_index, step, modality_index]), + "distance_after": float(predictions["distance_after"][sample_index, step, modality_index]), + "time_pool_weight_mean_over_paths": float(predictions["time_pool_weights"][sample_index, step]), + }) + return rows + + +def fit_temperature(probabilities: np.ndarray, class_y: np.ndarray) -> float: + def objective(log_temperature: float) -> float: + p = _regularized_logits(probabilities, float(np.exp(log_temperature))) + return float(-np.log(np.clip(p[np.arange(len(class_y)), class_y], 1e-12, 1.0)).mean()) + + fitted = minimize_scalar(objective, bounds=(-2.0, 2.0), method="bounded", options={"xatol": 1e-5}) + return float(np.exp(fitted.x)) + + +def _group_ids(original_mask: np.ndarray, hidden_mask: np.ndarray) -> np.ndarray: + # Group by which sources received new artificial gaps, not only by sources + # that were erased completely (the mask design deliberately preserves 20%). + original = np.asarray(original_mask, dtype=bool) + current = np.asarray(hidden_mask, dtype=bool) + hidden_mod = (original & ~current).any(axis=1) + bits = hidden_mod[:, 0].astype(int) + 2 * hidden_mod[:, 1].astype(int) + 4 * hidden_mod[:, 2].astype(int) + # PDF (5.62): total missing rate is computed per source on the valid time + # axis, then averaged equally across T/A/V (never weighted by feature size + # or by the number of naturally observed rows). + final_missing_by_modality = 1.0 - current.mean(axis=1) + realized_rate = final_missing_by_modality.mean(axis=1) + coarse = np.where(realized_rate <= 0.2, 0, np.where(realized_rate <= 0.5, 1, 2)) + return bits * 3 + coarse + + +def _missing_rate_summary(original: np.ndarray, current: np.ndarray) -> dict[str, Any]: + """Return PDF (5.62)–(5.63) rates, with equal modality weighting.""" + original = np.asarray(original, dtype=bool) + current = np.asarray(current, dtype=bool) + if original.shape != current.shape or original.ndim not in (2, 3): + raise ValueError("mask rate inputs must have matching [T,M] or [N,T,M] shapes") + newly_hidden = original & ~current + natural = 1.0 - original.mean(axis=-2) + final = 1.0 - current.mean(axis=-2) + observed_count = original.sum(axis=-2) + additional_count = newly_hidden.sum(axis=-2) + additional = np.divide( + additional_count, observed_count, + out=np.full(np.shape(additional_count), np.nan, dtype=np.float64), + where=observed_count > 0, + ) + synchronous = (~current.any(axis=-1)).mean(axis=-1) + return { + "natural_by_modality": natural, + "additional_by_modality": additional, + "final_by_modality": final, + "natural_global": float(np.mean(natural)), + "additional_global": float(np.nanmean(additional)), + "final_global": float(np.mean(final)), + "synchronous_no_observation": synchronous, + } + + +def smooth_group_risk( + losses: torch.Tensor, + group_ids: np.ndarray, + lambda_group: float = LAMBDA_GROUP, + group_temperature: float = GROUP_TEMPERATURE, +) -> torch.Tensor: + gids = torch.as_tensor(group_ids, device=losses.device, dtype=torch.long) + unique = torch.unique(gids) + group_losses, priors = [], [] + for group in unique: + selected = gids == group + group_losses.append(losses[selected].mean()) + priors.append(selected.float().mean()) + values = torch.stack(group_losses) + prior = torch.stack(priors).clamp_min(1e-8) + expected = (prior * values).sum() + worst = group_temperature * torch.logsumexp(torch.log(prior) + values / group_temperature, dim=0) + return (1.0 - lambda_group) * expected + lambda_group * worst + + +def _distillation_per_sample( + student: dict[str, Any], teacher: dict[str, Any], original: np.ndarray, current: np.ndarray, +) -> torch.Tensor: + temp = DISTILL_TEMPERATURE + p_teacher = teacher["tempered_probs_by_path"].mean(dim=0).detach().clamp_min(1e-8) + p_student = student["tempered_probs_by_path"].mean(dim=0).clamp_min(1e-8) + entropy = -(p_teacher * p_teacher.log()).sum(dim=-1) + confidence_weight = (1.0 - entropy / math.log(3.0)).clamp(0.0, 1.0) + retain_by_modality = [] + orig_t = torch.as_tensor(original, device=p_teacher.device, dtype=torch.float32) + curr_t = torch.as_tensor(current, device=p_teacher.device, dtype=torch.float32) + for m in range(len(MODALITIES)): + denominator = orig_t[:, :, m].sum(dim=1) + retained = (orig_t[:, :, m] * curr_t[:, :, m]).sum(dim=1) / denominator.clamp_min(1.0) + retain_by_modality.append(torch.where(denominator > 0, retained, torch.ones_like(retained))) + retain = torch.stack(retain_by_modality, dim=-1).mean(dim=-1) + weight = confidence_weight * retain + kl = (p_teacher * (p_teacher.log() - p_student.log())).sum(dim=-1) * temp * temp + teacher_score = teacher["mixed_score"].detach() + student_score = student["mixed_score"] + reg = F.huber_loss((teacher_score - student_score) / 3.0, torch.zeros_like(teacher_score), reduction="none", delta=0.25) + return weight * (kl + reg) + + +def fit_imputer( + imputer: StructuredGaussianImputer, + arrays: dict[str, np.ndarray], + split: SplitData, + device: torch.device, + epochs: int, + batch_size: int, + seed: int, +) -> list[dict[str, float]]: + imputer.train() + optimizer = torch.optim.AdamW(imputer.parameters(), lr=3e-4, weight_decay=1e-4) + rng = np.random.default_rng(seed) + history = [] + for epoch in range(1, epochs + 1): + order = rng.permutation(split.n) + losses = [] + for start in range(0, split.n, batch_size): + idx = order[start:start + batch_size] + xs, observed = to_device_batch(arrays, split.mask, idx, device) + nll = imputer.observed_nll(xs, observed).mean() + observed_scalars = sum(observed[:, :, m].sum(dim=1).float() * INPUT_DIMS[m] for m in range(len(MODALITIES))) + emission_penalty = sum(value.square().mean() for value in imputer.emissions()) + transition_penalty = imputer._transition().square().mean() + loss = nll / observed_scalars.mean().clamp_min(1.0) + loss = loss + LAMBDA_EMISSION * emission_penalty + LAMBDA_TRANSITION * transition_penalty + optimizer.zero_grad(set_to_none=True) + loss.backward() + torch.nn.utils.clip_grad_norm_(imputer.parameters(), 5.0) + optimizer.step() + losses.append(float(loss.detach().cpu())) + row = {"stage": "structured_imputer", "epoch": epoch, "train_observed_nll_per_scalar": float(np.mean(losses))} + history.append(row) + print(f"imputer {epoch}/{epochs}: observed_nll/scalar={row['train_observed_nll_per_scalar']:.4f}", flush=True) + imputer.eval() + for parameter in imputer.parameters(): + parameter.requires_grad_(False) + return history + + +def _make_variant( + name: str, + imputer: StructuredGaussianImputer, + reliability_hparams: tuple[float, float, float, float] = DEFAULT_RELIABILITY, +) -> CRG: + if name == "C1": + flags = dict(use_imputer=False, use_joint_draws=False, use_final_gate=False, use_source_attention=False, reliability_update=False, use_low_rank=False) + elif name == "C2": + flags = dict(use_imputer=True, use_joint_draws=False, use_final_gate=False, use_source_attention=False, reliability_update=False, use_low_rank=False) + elif name == "C3": + flags = dict(use_imputer=True, use_joint_draws=True, use_final_gate=True, use_source_attention=False, reliability_update=False, use_low_rank=False) + elif name == "C4": + flags = dict(use_imputer=True, use_joint_draws=True, use_final_gate=True, use_source_attention=True, reliability_update=False, use_low_rank=False) + else: + flags = dict(use_imputer=True, use_joint_draws=True, use_final_gate=True, use_source_attention=True, + reliability_update=True, use_low_rank=name == "C6" or name.startswith("C7")) + return CRG(copy.deepcopy(imputer), reliability_hparams=reliability_hparams, **flags) + + +def _set_reliability_hparams(model: CRG, values: tuple[float, float, float, float]) -> None: + rho_imp, lambda_u, lambda_gap, lambda_span = values + if not 0.0 < rho_imp < 1.0 or min(lambda_u, lambda_gap, lambda_span) < 0.0: + raise ValueError("invalid reliability hyperparameters") + with torch.no_grad(): + model.rho_imp.fill_(rho_imp) + model.rel_u.fill_(lambda_u) + model.rel_gap.fill_(lambda_gap) + model.rel_span.fill_(lambda_span) + + +def tune_reliability_hparams( + model: CRG, + arrays: dict[str, np.ndarray], + split: SplitData, + scenarios: dict[str, np.ndarray], + device: torch.device, + batch_size: int, + model_name: str, + seed: int = SEED + 551, + candidate_values: tuple[tuple[float, float, float, float], ...] = RELIABILITY_CANDIDATES, +) -> tuple[tuple[float, float, float, float], list[dict[str, Any]]]: + if not (model.use_final_gate or model.use_source_attention or model.reliability_update): + return DEFAULT_RELIABILITY, [{"model": model_name, "selected": True, + "rho_imp": DEFAULT_RELIABILITY[0], "lambda_u": DEFAULT_RELIABILITY[1], + "lambda_gap": DEFAULT_RELIABILITY[2], "lambda_span": DEFAULT_RELIABILITY[3], + "inner_selection_nll": float("nan"), "note": "not used by this ablation"}] + rows = [] + best_values, best_loss = DEFAULT_RELIABILITY, float("inf") + if not candidate_values: + raise ValueError("at least one reliability candidate is required") + for values in candidate_values: + _set_reliability_hparams(model, values) + scenario_losses: dict[str, float] = {} + for scenario, masks in scenarios.items(): + with fixed_torch_seed(_scenario_seed(seed, split.name, scenario), device): + metrics, _ = evaluate(model, arrays, split, device, batch_size, masks=masks) + scenario_losses[scenario] = float(metrics["selection_nll"]) + score = float(np.mean(list(scenario_losses.values()))) + row = {"model": model_name, "rho_imp": values[0], "lambda_u": values[1], + "lambda_gap": values[2], "lambda_span": values[3], "inner_selection_nll": score, + "scenario_selection_nll": json.dumps(scenario_losses, sort_keys=True), + "selected": False, "selection_split": split.name} + rows.append(row) + if score < best_loss: + best_loss, best_values = score, values + _set_reliability_hparams(model, best_values) + for row in rows: + row["selected"] = (row["rho_imp"], row["lambda_u"], row["lambda_gap"], row["lambda_span"]) == best_values + return best_values, rows + + +def tune_group_risk_model( + imputer: StructuredGaussianImputer, + train: SplitData, + valid: SplitData, + arrays: dict[str, dict[str, np.ndarray]], + reliability_validation: SplitData, + reliability_arrays: dict[str, np.ndarray], + reliability_scenarios: dict[str, np.ndarray], + device: torch.device, + epochs: int, + batch_size: int, + patience: int, + seed: int, +) -> tuple[CRG, list[dict[str, float]], list[dict[str, Any]], list[dict[str, Any]], tuple[float, float, float, float], tuple[float, float], float]: + candidates = [] + all_history: list[dict[str, float]] = [] + all_reliability_rows: list[dict[str, Any]] = [] + risk_rows: list[dict[str, Any]] = [] + for candidate_index, (group_lambda, group_temperature) in enumerate(GROUP_RISK_CANDIDATES): + candidate_name = f"C7_group_lambda{group_lambda:.2f}_tau{group_temperature:.2f}" + model = _make_variant("C7_group", imputer) + with fixed_torch_seed(seed + 303, device): + model, history = _fit_neural( + model, candidate_name, train, valid, arrays, device, epochs, batch_size, patience, + np.random.default_rng(seed + 303), use_group_risk=True, + group_lambda=group_lambda, group_temperature=group_temperature, + selection_split=reliability_validation, selection_arrays=reliability_arrays, + selection_scenarios=reliability_scenarios, + ) + all_history.extend(history) + selected_reliability, tuning_rows = tune_reliability_hparams( + model, reliability_arrays, reliability_validation, reliability_scenarios, + device, batch_size, candidate_name, seed=seed + 551, + ) + selected_row = next(row for row in tuning_rows if row.get("selected")) + inner_score = float(selected_row["inner_selection_nll"]) + for row in tuning_rows: + row["group_lambda"] = group_lambda + row["group_temperature"] = group_temperature + row["risk_candidate_selected"] = False + row["candidate"] = row["model"] + row["model"] = "C7_group" + all_reliability_rows.extend(tuning_rows) + candidates.append((inner_score, model, selected_reliability, (group_lambda, group_temperature), candidate_name)) + risk_rows.append({"model": "C7_group", "candidate": candidate_name, + "lambda_group": group_lambda, "group_temperature": group_temperature, + "selected_reliability": selected_reliability, + "inner_selection_nll": inner_score, "selected": False, + "selection_split": reliability_validation.name}) + best = min(candidates, key=lambda row: row[0]) + score, model, selected_reliability, selected_group, candidate_name = best + for row in all_reliability_rows: + row["risk_candidate_selected"] = row["candidate"] == candidate_name + row["selected"] = bool(row.get("selected") and row["risk_candidate_selected"]) + for row in risk_rows: + row["selected"] = row["candidate"] == candidate_name + if row["selected"]: + row["selected"] = True + return model, all_history, all_reliability_rows, risk_rows, selected_reliability, selected_group, score + + +def _fit_neural( + model: CRG, + name: str, + train: SplitData, + valid: SplitData, + arrays: dict[str, dict[str, np.ndarray]], + device: torch.device, + epochs: int, + batch_size: int, + patience: int, + rng: np.random.Generator, + *, + teacher: CRG | None = None, + use_group_risk: bool = False, + group_lambda: float = LAMBDA_GROUP, + group_temperature: float = GROUP_TEMPERATURE, + selection_split: SplitData | None = None, + selection_arrays: dict[str, np.ndarray] | None = None, + selection_scenarios: dict[str, np.ndarray] | None = None, + mask_kind: str = "continuous", + use_reconstruction: bool = True, +) -> tuple[CRG, list[dict[str, float]]]: + model.to(device) + model.imputer.eval() + for parameter in model.imputer.parameters(): + parameter.requires_grad_(False) + if teacher is not None: + teacher.eval() + for parameter in teacher.parameters(): + parameter.requires_grad_(False) + optimizer = torch.optim.AdamW([p for p in model.parameters() if p.requires_grad], lr=3e-4, weight_decay=1e-3) + best_loss, best_state, stale = float("inf"), None, 0 + history: list[dict[str, float]] = [] + rate_choices = np.asarray(MASK_RATES, dtype=np.float64) + pattern_choices = np.asarray(MASK_MODES, dtype=object) + for epoch in range(1, epochs + 1): + model.train() + order = rng.permutation(train.n) + epoch_losses = [] + for start in range(0, train.n, batch_size): + idx = order[start:start + batch_size] + xs, natural = to_device_batch(arrays["fit"], train.mask, idx, device) + if name == "teacher": + rates = np.zeros(len(idx), dtype=np.float64) + modes = ["none"] * len(idx) + else: + rates = rng.choice(rate_choices, size=len(idx), p=np.asarray([0.2] * 5)) + modes = rng.choice(pattern_choices, size=len(idx)).tolist() + masks_np = np.stack([ + continuous_mask(train.mask[int(i)], float(rate), str(mode), rng, kind=mask_kind) + for i, rate, mode in zip(idx, rates, modes) + ]) + current = torch.as_tensor(masks_np, device=device, dtype=torch.bool) + class_y = torch.as_tensor(train.class_y[idx], device=device, dtype=torch.long) + regression_y = torch.as_tensor(train.regression_y[idx], device=device, dtype=torch.float32) + output = model(xs, current, paths=4 if model.use_joint_draws else 1, joint_draws=model.use_joint_draws) + supervised, _ = supervised_loss_per_sample(output, class_y, regression_y) + hidden_np = train.mask[idx] & ~masks_np + hidden = torch.as_tensor(hidden_np, device=device, dtype=torch.bool) + reconstruction = reconstruction_loss_per_sample(output, xs, hidden) + per_sample = supervised + (LAMBDA_RECON * reconstruction if use_reconstruction else 0.0) + if teacher is not None: + with torch.no_grad(): + teacher_output = teacher(xs, natural, paths=4, joint_draws=True) + distill = _distillation_per_sample( output, teacher_output, train.mask[idx], masks_np) + per_sample = per_sample + LAMBDA_DISTILL * distill + if use_group_risk: + group_ids = _group_ids(train.mask[idx], masks_np) + loss = smooth_group_risk(per_sample, group_ids, group_lambda, group_temperature) + else: + loss = per_sample.mean() + optimizer.zero_grad(set_to_none=True) + loss.backward() + torch.nn.utils.clip_grad_norm_([p for p in model.parameters() if p.requires_grad], 1.0) + optimizer.step() + epoch_losses.append(float(loss.detach().cpu())) + if selection_split is not None and selection_arrays is not None and selection_scenarios: + selected_metrics = {} + for scenario, masks in selection_scenarios.items(): + scenario_metrics, _ = evaluate(model, selection_arrays, selection_split, device, batch_size, masks=masks) + selected_metrics[scenario] = scenario_metrics + val_loss = float(np.mean([metrics["selection_nll"] for metrics in selected_metrics.values()])) + natural_metrics = next((metrics for key, metrics in selected_metrics.items() + if key.startswith("0.0/") or key.endswith("natural")), + next(iter(selected_metrics.values()))) + selection_source = "group_disjoint_internal_scenarios" + else: + natural_metrics, _ = evaluate(model, arrays["valid"], valid, device, batch_size) + val_loss = float(natural_metrics["selection_nll"]) + selection_source = "official_valid_fallback" + row = {"stage": name, "epoch": epoch, "train_loss": float(np.mean(epoch_losses)), + "inner_selection_nll": val_loss, "inner_natural_accuracy": natural_metrics["accuracy"], + "inner_natural_macro_f1": natural_metrics["macro_f1"], + "inner_natural_mae": natural_metrics["regression_mae"], + "selection_source": selection_source} + history.append(row) + print(f"{name} {epoch}/{epochs}: train={row['train_loss']:.4f} innerNLL={val_loss:.4f} " + f"acc={row['inner_natural_accuracy']:.4f} macroF1={row['inner_natural_macro_f1']:.4f}", flush=True) + if val_loss < best_loss: + best_loss, best_state, stale = val_loss, copy.deepcopy(model.state_dict()), 0 + else: + stale += 1 + if stale >= patience: + break + if best_state is not None: + model.load_state_dict(best_state) + model.eval() + return model, history + + +def _sample_statistics( + split: SplitData, arrays: dict[str, np.ndarray], indices: np.ndarray, masks: np.ndarray | None = None, +) -> np.ndarray: + parts = [] + effective_mask = split.mask if masks is None else np.asarray(masks, dtype=bool) + for modality_index, modality in enumerate(MODALITIES): + x = arrays[modality][indices] + mask = effective_mask[indices, :, modality_index] + count = mask.sum(axis=1, keepdims=True) + mean = (x * mask[:, :, None]).sum(axis=1) / np.maximum(count, 1) + variance = (((x - mean[:, None, :]) ** 2) * mask[:, :, None]).sum(axis=1) / np.maximum(count, 1) + missing = 1.0 - mask.mean(axis=1, keepdims=True) + max_gap = [] + for row in mask: + longest = current = 0 + for visible in row: + current = 0 if visible else current + 1 + longest = max(longest, current) + max_gap.append(longest / max(1, len(row))) + parts.extend((mean, np.sqrt(variance), missing, np.asarray(max_gap, np.float32)[:, None])) + return np.concatenate(parts, axis=1).astype(np.float32) + + +def fit_c0( + train: SplitData, + valid: SplitData, + arrays: dict[str, dict[str, np.ndarray]], +) -> tuple[dict[str, Any], dict[str, Any]]: + train_x = _sample_statistics(train, arrays["fit"], np.arange(train.n)) + classifier = LogisticRegression(C=0.05, max_iter=2500, random_state=SEED) + classifier.fit(train_x, train.class_y) + regressor = Ridge(alpha=25.0) + regressor.fit(train_x, train.regression_y) + return {}, {"classifier": classifier, "regressor": regressor} + + +def calibrate_c0_interval( + state: dict[str, Any], + calibration: SplitData, + arrays: dict[str, np.ndarray], +) -> None: + features = _sample_statistics(calibration, arrays, np.arange(calibration.n)) + residual = calibration.regression_y - state["regressor"].predict(features) + state["residual_q05"], state["residual_q95"] = ( + float(np.quantile(residual, 0.05)), float(np.quantile(residual, 0.95)) + ) + + +def evaluate_c0( + state: dict[str, Any], split: SplitData, arrays: dict[str, np.ndarray], + temperature: float = 1.0, masks: np.ndarray | None = None, +) -> tuple[dict[str, Any], dict[str, np.ndarray]]: + features = _sample_statistics(split, arrays, np.arange(split.n), masks) + classifier, regressor = state["classifier"], state["regressor"] + raw = np.zeros((split.n, 3), np.float64) + raw[:, classifier.classes_] = classifier.predict_proba(features) + probs = _regularized_logits(raw, temperature) + maximum = probs.max(axis=1, keepdims=True) + ties = np.isclose(probs, maximum, rtol=0.0, atol=1e-12) + pred_class = np.asarray([next(c for c in (1, 0, 2) if row[c]) for row in ties], dtype=np.int64) + score = np.clip(regressor.predict(features), -3.0, 3.0) + interval_lower = np.clip(score + state["residual_q05"], -3.0, 3.0) + interval_upper = np.clip(score + state["residual_q95"], -3.0, 3.0) + metrics = calculate_metrics( + split.class_y, split.regression_y, probs, pred_class, score, + interval_lower=interval_lower, interval_upper=interval_upper, + ) + scaled_error = torch.as_tensor((split.regression_y - score) / 3.0, dtype=torch.float32) + metrics["selection_nll"] = metrics["classification_nll"] + float( + F.huber_loss(scaled_error, torch.zeros_like(scaled_error), delta=0.25) + ) + return metrics, {"probabilities": probs, "predicted_class": pred_class, "predicted_score": score, + "interval_lower": interval_lower, "interval_upper": interval_upper} + + +def controlled_c0( + state: dict[str, Any], split: SplitData, arrays: dict[str, np.ndarray], + scenarios: dict[str, np.ndarray], temperature: float, +) -> tuple[list[dict[str, Any]], dict[str, dict[str, np.ndarray]]]: + rows = [] + predictions = {} + for scenario, mask in scenarios.items(): + metrics, prediction = evaluate_c0(state, split, arrays, temperature, mask) + predictions[scenario] = prediction + rates = _missing_rate_summary(split.mask, mask) + additional_by_modality = np.nanmean(rates["additional_by_modality"], axis=0) + rate, mode = scenario.split("/", 1) + rows.append({"evaluation_split": split.name, "model": "C0", "rate_requested_per_selected_source": float(rate), + "mask_pattern": mode, "rate_realized_global": rates["additional_global"], + "rate_realized_additional_global": rates["additional_global"], + "rate_realized_additional_by_modality": json.dumps([None if not np.isfinite(x) else float(x) for x in additional_by_modality]), + "natural_missing_rate_global": rates["natural_global"], + "natural_missing_rate_by_modality": json.dumps(np.mean(rates["natural_by_modality"], axis=0).tolist()), + "rate_final_total_missing_global": rates["final_global"], + "rate_final_total_missing_by_modality": json.dumps(np.mean(rates["final_by_modality"], axis=0).tolist()), + "synchronous_no_observation_rate": float(np.mean(rates["synchronous_no_observation"])), **metrics}) + return rows, predictions + + +def write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + if not rows: + return + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8-sig", newline="") as stream: + fieldnames = list(dict.fromkeys(key for row in rows for key in row)) + writer = csv.DictWriter(stream, fieldnames=fieldnames, extrasaction="raise") + writer.writeheader() + writer.writerows(rows) + + +def mask_audit_rows(split: SplitData, scenarios: dict[str, np.ndarray], seed: int) -> list[dict[str, Any]]: + rows: list[dict[str, Any]] = [] + for scenario, masks in scenarios.items(): + requested_rate, pattern = scenario.split("/", 1) + for index, (sample_id, original, current) in enumerate(zip(split.ids, split.mask, masks)): + newly_hidden = np.asarray(original, dtype=bool) & ~np.asarray(current, dtype=bool) + intervals: dict[str, list[list[int]]] = {} + for modality_index, modality in enumerate(MODALITIES): + positions = np.flatnonzero(newly_hidden[:, modality_index]) + spans: list[list[int]] = [] + if len(positions): + start = previous = int(positions[0]) + for position in positions[1:]: + position = int(position) + if position != previous + 1: + spans.append([start, previous + 1]) + start = position + previous = position + spans.append([start, previous + 1]) + intervals[modality] = spans + rate_summary = _missing_rate_summary(original, current) + rows.append({ + "natural_missing_rate": rate_summary["natural_global"], + "natural_missing_rate_by_modality": json.dumps(rate_summary["natural_by_modality"].tolist()), + "realized_additional_rate": rate_summary["additional_global"], + "realized_additional_rate_by_modality": json.dumps([ + None if not np.isfinite(x) else float(x) + for x in rate_summary["additional_by_modality"] + ]), + "final_total_missing_rate": rate_summary["final_global"], + "final_total_missing_rate_by_modality": json.dumps(rate_summary["final_by_modality"].tolist()), + "synchronous_no_observation_rate": float(rate_summary["synchronous_no_observation"]), + "sample_id": sample_id, + "video_id": split.groups[index], + "scenario": scenario, + "requested_rate_per_selected_source": float(requested_rate), + "mask_pattern": pattern, + "mask_seed": _scenario_seed(seed, sample_id, scenario), + "selected_modalities": ",".join(m for m in MODALITIES if intervals[m]), + "newly_hidden_intervals_step_half_open": json.dumps(intervals, separators=(",", ":")), + "original_observed_steps": int(np.asarray(original, dtype=bool).sum()), + "newly_hidden_steps": int(newly_hidden.sum()), + }) + return rows + + +def group_bootstrap( + split: SplitData, prediction: dict[str, np.ndarray], reps: int = 1000, seed: int = SEED + 44, +) -> list[dict[str, Any]]: + rng = np.random.default_rng(seed) + groups = np.unique(split.groups) + group_indices = {g: np.flatnonzero(split.groups == g) for g in groups} + rows = {name: [] for name in ("accuracy", "macro_f1", "mae", "rmse", "pearson", "interval_90_coverage", "interval_90_mean_width")} + for _ in range(reps): + chosen = rng.choice(groups, size=len(groups), replace=True) + idx = np.concatenate([group_indices[g] for g in chosen]) + cls, score = prediction["predicted_class"][idx], prediction["predicted_score"][idx] + ycls, yreg = split.class_y[idx], split.regression_y[idx] + rows["accuracy"].append(accuracy_score(ycls, cls)) + rows["macro_f1"].append(f1_score(ycls, cls, labels=[0, 1, 2], average="macro", zero_division=0)) + rows["mae"].append(mean_absolute_error(yreg, score)) + rows["rmse"].append(np.sqrt(mean_squared_error(yreg, score))) + rows["pearson"].append(pearsonr(yreg, score).statistic if np.std(score) and np.std(yreg) else np.nan) + rows["interval_90_coverage"].append(np.mean((yreg >= prediction["interval_lower"][idx]) & (yreg <= prediction["interval_upper"][idx]))) + rows["interval_90_mean_width"].append(np.mean(prediction["interval_upper"][idx] - prediction["interval_lower"][idx])) + result = [] + for name, values in rows.items(): + values = np.asarray(values, dtype=np.float64) + result.append({"metric": name, "estimate": float(np.nanmedian(values)), + "ci_2_5": float(np.nanpercentile(values, 2.5)), + "ci_97_5": float(np.nanpercentile(values, 97.5)), + "replicates": reps, "unit": "source video group"}) + return result + + +def paired_group_bootstrap_deltas( + split: SplitData, + predictions: dict[str, dict[str, np.ndarray]], + reps: int = 1000, + seed: int = SEED + 88, +) -> list[dict[str, Any]]: + """Paired validation-set model deltas using one shared source-video resample.""" + groups = np.unique(split.groups) + group_indices = {group: np.flatnonzero(split.groups == group) for group in groups} + compare_models = [name for name in predictions if name != "C0"] + metrics = ("accuracy", "macro_f1", "mae", "rmse", "pearson", "interval_90_coverage", "interval_90_mean_width") + + def value(name: str, metric: str, indices: np.ndarray) -> float: + pred = predictions[name] + y_cls, y_reg = split.class_y[indices], split.regression_y[indices] + if metric == "accuracy": + return float(accuracy_score(y_cls, pred["predicted_class"][indices])) + if metric == "macro_f1": + return float(f1_score(y_cls, pred["predicted_class"][indices], labels=[0, 1, 2], average="macro", zero_division=0)) + if metric == "mae": + return float(mean_absolute_error(y_reg, pred["predicted_score"][indices])) + if metric == "rmse": + return float(np.sqrt(mean_squared_error(y_reg, pred["predicted_score"][indices]))) + if metric == "pearson": + estimate = pred["predicted_score"][indices] + return float(pearsonr(y_reg, estimate).statistic) if np.std(y_reg) and np.std(estimate) else float("nan") + if metric == "interval_90_coverage": + return float(np.mean((y_reg >= pred["interval_lower"][indices]) & (y_reg <= pred["interval_upper"][indices]))) + if metric == "interval_90_mean_width": + return float(np.mean(pred["interval_upper"][indices] - pred["interval_lower"][indices])) + raise ValueError(metric) + + point = { + (model, metric): value(model, metric, np.arange(split.n)) - value("C0", metric, np.arange(split.n)) + for model in compare_models for metric in metrics + } + draws = {key: [] for key in point} + rng = np.random.default_rng(seed) + for _ in range(reps): + chosen = rng.choice(groups, size=len(groups), replace=True) + indices = np.concatenate([group_indices[group] for group in chosen]) + baseline = {metric: value("C0", metric, indices) for metric in metrics} + for model in compare_models: + for metric in metrics: + draws[(model, metric)].append(value(model, metric, indices) - baseline[metric]) + rows = [] + for (model, metric), values in draws.items(): + values = np.asarray(values, dtype=np.float64) + rows.append({ + "model": model, "baseline": "C0", "metric": metric, + "point_delta": point[(model, metric)], + "bootstrap_median_delta": float(np.nanmedian(values)), + "ci_2_5": float(np.nanpercentile(values, 2.5)), + "ci_97_5": float(np.nanpercentile(values, 97.5)), + "replicates": reps, "unit": "paired source-video group resample", + }) + return rows + + +def controlled_metrics( + model: CRG, + split: SplitData, + arrays: dict[str, np.ndarray], + scenarios: dict[str, np.ndarray], + device: torch.device, + batch_size: int, + model_name: str, + temperature: float, + seed: int = SEED + 552, +) -> tuple[list[dict[str, Any]], dict[str, dict[str, np.ndarray]]]: + rows = [] + predictions = {} + for scenario, mask in scenarios.items(): + scenario_seed = _scenario_seed(seed, split.name, scenario) + with fixed_torch_seed(scenario_seed, device): + metrics, prediction = evaluate(model, arrays, split, device, batch_size, masks=mask, temperature=temperature) + predictions[scenario] = prediction + rates = _missing_rate_summary(split.mask, mask) + additional_by_modality = np.nanmean(rates["additional_by_modality"], axis=0) + rate, mode = scenario.split("/", 1) + rows.append({"evaluation_split": split.name, "model": model_name, "rate_requested_per_selected_source": float(rate), + "mask_pattern": mode, "rate_realized_global": rates["additional_global"], + "rate_realized_additional_global": rates["additional_global"], + "rate_realized_additional_by_modality": json.dumps([None if not np.isfinite(x) else float(x) for x in additional_by_modality]), + "natural_missing_rate_global": rates["natural_global"], + "natural_missing_rate_by_modality": json.dumps(np.mean(rates["natural_by_modality"], axis=0).tolist()), + "rate_final_total_missing_global": rates["final_global"], + "rate_final_total_missing_by_modality": json.dumps(np.mean(rates["final_by_modality"], axis=0).tolist()), + "synchronous_no_observation_rate": float(np.mean(rates["synchronous_no_observation"])), **metrics}) + print(f"validation mask {scenario}: additional={rates['additional_global']:.3f} final={rates['final_global']:.3f} " + f"macroF1={metrics['macro_f1']:.4f} MAE={metrics['regression_mae']:.4f}", flush=True) + return rows, predictions + + +def controlled_group_bootstrap( + split: SplitData, + predictions: dict[str, dict[str, dict[str, np.ndarray]]], + scenario_masks: dict[str, np.ndarray], + repeats: int, + seed: int, +) -> list[dict[str, Any]]: + """Paired source-video bootstrap for each fixed mask and the MAE-rate AURC.""" + if "C0" not in predictions: + raise ValueError("controlled bootstrap requires C0 predictions") + scenarios = list(predictions["C0"]) + if any(set(model_predictions) != set(scenarios) for model_predictions in predictions.values()): + raise ValueError("all models must use identical controlled-mask scenarios") + if set(scenario_masks) != set(scenarios): + raise ValueError("controlled-mask audit and model predictions must use identical scenarios") + groups = np.unique(split.groups) + group_indices = {group: np.flatnonzero(split.groups == group) for group in groups} + metric_names = ("accuracy", "macro_f1", "mae", "rmse", "pearson") + keys = [(model, scenario, metric) for model in predictions for scenario in scenarios for metric in metric_names] + point = {} + draws = {key: [] for key in keys} + delta_draws = {key: [] for key in keys if key[0] != "C0"} + + curve_modes = tuple(MASK_MODES) + curve_rates = np.asarray((0.0, 0.1, 0.3, 0.5, 0.7), dtype=np.float64) + curve_scenarios = { + mode: tuple("0.0/none" if rate == 0.0 else f"{rate:.1f}/{mode}" for rate in curve_rates) + for mode in curve_modes + } + for mode, curve in curve_scenarios.items(): + if any(scenario not in scenarios for scenario in curve): + raise ValueError(f"missing fixed rate-curve scenario for {mode}") + curve_point = {} + curve_draws = {} + curve_actual_rates: dict[tuple[str, str], np.ndarray] = {} + additional_rate_by_sample: dict[str, np.ndarray] = {} + for scenario, mask in scenario_masks.items(): + by_modality = _missing_rate_summary(split.mask, mask)["additional_by_modality"] + counts = np.isfinite(by_modality).sum(axis=1) + additional_rate_by_sample[scenario] = np.divide( + np.nansum(by_modality, axis=1), counts, + out=np.full(split.n, np.nan, dtype=np.float64), where=counts > 0, + ) + for model in predictions: + for mode, curve in curve_scenarios.items(): + key = (model, mode) + errors = [np.abs(split.regression_y - predictions[model][scenario]["predicted_score"]) for scenario in curve] + point_curve = np.asarray([float(error.mean()) for error in errors]) + actual_rates = np.asarray([ + 0.0 if scenario == "0.0/none" else float(np.nanmean(additional_rate_by_sample[scenario])) + for scenario in curve + ]) + curve_actual_rates[model, mode] = actual_rates + order = np.argsort(actual_rates, kind="stable") + x = actual_rates[order] + y = point_curve[order] + unique_x, inverse = np.unique(x, return_inverse=True) + unique_y = np.asarray([y[inverse == index].mean() for index in range(len(unique_x))]) + curve_point[key] = (float(np.trapezoid(unique_y, unique_x) / unique_x[-1]) + if len(unique_x) > 1 and unique_x[-1] > 0.0 else float(point_curve[0])) + curve_draws[key] = [] + + def metric_values(model: str, scenario: str, indices: np.ndarray) -> dict[str, float]: + prediction = predictions[model][scenario] + y_class, y_score = split.class_y[indices], split.regression_y[indices] + predicted_class = prediction["predicted_class"][indices] + predicted_score = prediction["predicted_score"][indices] + return { + "accuracy": float(accuracy_score(y_class, predicted_class)), + "macro_f1": float(f1_score(y_class, predicted_class, labels=[0, 1, 2], average="macro", zero_division=0)), + "mae": float(mean_absolute_error(y_score, predicted_score)), + "rmse": float(np.sqrt(mean_squared_error(y_score, predicted_score))), + "pearson": float(pearsonr(y_score, predicted_score).statistic) + if np.std(y_score) and np.std(predicted_score) else float("nan"), + } + + for model in predictions: + for scenario in scenarios: + values = metric_values(model, scenario, np.arange(split.n)) + for metric, value in values.items(): + point[(model, scenario, metric)] = value + + within_model_mae_delta_draws = { + (model, scenario): [] for model in predictions for scenario in scenarios if scenario != "0.0/none" + } + + rng = np.random.default_rng(seed) + for _ in range(repeats): + chosen = rng.choice(groups, size=len(groups), replace=True) + indices = np.concatenate([group_indices[group] for group in chosen]) + replicate_values = {} + for model in predictions: + for scenario in scenarios: + values = metric_values(model, scenario, indices) + for metric, value in values.items(): + key = (model, scenario, metric) + draws[key].append(value) + replicate_values[key] = value + for model in predictions: + if model == "C0": + continue + for scenario in scenarios: + for metric in metric_names: + key = (model, scenario, metric) + delta_draws[key].append(replicate_values[key] - replicate_values[("C0", scenario, metric)]) + for model in predictions: + natural_mae = replicate_values[(model, "0.0/none", "mae")] + for scenario in scenarios: + if scenario != "0.0/none": + within_model_mae_delta_draws[(model, scenario)].append( + replicate_values[(model, scenario, "mae")] - natural_mae + ) + for model in predictions: + for mode, curve in curve_scenarios.items(): + curve_mae = [replicate_values[(model, scenario, "mae")] for scenario in curve] + sample_rates = np.asarray([ + 0.0 if scenario == "0.0/none" else float(np.nanmean(additional_rate_by_sample[scenario][indices])) + for scenario in curve + ]) + order = np.argsort(sample_rates, kind="stable") + x = sample_rates[order] + y = np.asarray(curve_mae)[order] + unique_x, inverse = np.unique(x, return_inverse=True) + unique_y = np.asarray([y[inverse == index].mean() for index in range(len(unique_x))]) + auc = (float(np.trapezoid(unique_y, unique_x) / unique_x[-1]) + if len(unique_x) > 1 and unique_x[-1] > 0.0 else float(curve_mae[0])) + curve_draws[(model, mode)].append(auc) + + def interval(values: list[float]) -> tuple[float, float, float]: + samples = np.asarray(values, dtype=np.float64) + return float(np.nanmedian(samples)), float(np.nanpercentile(samples, 2.5)), float(np.nanpercentile(samples, 97.5)) + + rows = [] + for model, scenario, metric in keys: + median, lower, upper = interval(draws[(model, scenario, metric)]) + row = {"model": model, "scenario": scenario, "metric": metric, + "estimate": point[(model, scenario, metric)], "bootstrap_median": median, + "ci_2_5": lower, "ci_97_5": upper, "replicates": repeats, + "unit": "paired source-video group resample"} + if model != "C0": + delta_median, delta_lower, delta_upper = interval(delta_draws[(model, scenario, metric)]) + row.update({"baseline": "C0", "delta_estimate": point[(model, scenario, metric)] - point[("C0", scenario, metric)], + "delta_bootstrap_median": delta_median, "delta_ci_2_5": delta_lower, + "delta_ci_97_5": delta_upper}) + if scenario != "0.0/none" and metric == "mae": + natural = point[(model, "0.0/none", "mae")] + within_median, within_lower, within_upper = interval(within_model_mae_delta_draws[(model, scenario)]) + row.update({"delta_to_natural_mae": point[(model, scenario, "mae")] - natural, + "delta_to_natural_bootstrap_median": within_median, + "delta_to_natural_ci_2_5": within_lower, + "delta_to_natural_ci_97_5": within_upper}) + rows.append(row) + for model in predictions: + for mode in curve_modes: + median, lower, upper = interval(curve_draws[(model, mode)]) + row = {"model": model, "scenario": f"MAE_rate_curve/{mode}", "metric": "AURC_MAE", + "estimate": curve_point[(model, mode)], "bootstrap_median": median, + "ci_2_5": lower, "ci_97_5": upper, + "curve_additional_rates_realized": json.dumps(curve_actual_rates[(model, mode)].tolist()), + "replicates": repeats, "unit": "paired source-video group resample"} + if model != "C0": + baseline_values = curve_draws[("C0", mode)] + deltas = np.asarray(curve_draws[(model, mode)]) - np.asarray(baseline_values) + delta_median, delta_lower, delta_upper = interval(deltas.tolist()) + row.update({"baseline": "C0", "delta_estimate": curve_point[(model, mode)] - curve_point[("C0", mode)], + "delta_bootstrap_median": delta_median, "delta_ci_2_5": delta_lower, + "delta_ci_97_5": delta_upper}) + rows.append(row) + return rows + + +def _decode_attachment_text(value: Any) -> str: + if isinstance(value, bytes): + return value.decode("utf-8", errors="replace") + if isinstance(value, np.bytes_): + return bytes(value).decode("utf-8", errors="replace") + if isinstance(value, np.ndarray): + if value.shape == (): + return _decode_attachment_text(value.item()) + return " ".join(_decode_attachment_text(item) for item in value.reshape(-1)) + if isinstance(value, (list, tuple)): + return " ".join(_decode_attachment_text(item) for item in value) + return str(value) + + +def _fixed_source_rows(value: Any, dim: int, field: str) -> tuple[np.ndarray, int]: + rows = np.asarray(value, dtype=np.float32) + if rows.ndim == 3 and rows.shape[0] == 1: + rows = rows[0] + if rows.ndim != 2 or rows.shape[1] != dim: + raise ValueError(f"attachment-3 {field}: expected (steps,{dim}), got {rows.shape}") + if not np.isfinite(rows).all(): + raise ValueError(f"attachment-3 {field}: non-finite feature values") + present = np.any(rows != 0, axis=1) + last = int(np.flatnonzero(present)[-1]) + 1 if present.any() else 0 + fitted = np.zeros((500, dim), np.float32) + fitted[: min(500, len(rows))] = rows[:500] + return fitted, min(500, last) + + +def reencode_attachment3( + device: torch.device, input_version: str = "aligned_50" +) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + from .data import restricted_load + + input_dir = ATTACHMENT3_UNALIGNED if input_version == "unaligned_50" else ATTACHMENT3_ALIGNED + if input_version == "unaligned_50": + files = sorted(input_dir.glob("*.pkl"), key=lambda p: p.name) + else: + files = sorted(input_dir.glob("附件3_*.pkl"), key=lambda p: int(p.stem.split("_")[-1])) + if len(files) != 30: + raise FileNotFoundError(f"expected 30 attachment-3 files, found {len(files)} under {input_dir}") + file_ids = [path.stem for path in files] + if len(set(file_ids)) != len(file_ids): + raise ValueError("attachment-3 input file IDs are not unique") + bert = AutoModel.from_pretrained(TEXT_MODEL_ID).to(device).eval() + tokenizer = AutoTokenizer.from_pretrained(TEXT_MODEL_ID, use_fast=True) if input_version == "unaligned_50" else None + cases, audit = [], [] + with torch.inference_mode(): + for path in files: + case_id = path.stem + if input_version == "unaligned_50": + loaded = restricted_load(path) + raw = loaded.get("test", loaded) + raw_text = _decode_attachment_text(raw.get("raw_text", "")).strip() + if not raw_text: + raise ValueError(f"{path.name}: raw_text is empty; cannot reconstruct the Q2 text input") + tokens = tokenizer(raw_text, truncation=True, padding="max_length", max_length=50, return_tensors="pt") + ids = tokens["input_ids"].to(device) + attention = tokens["attention_mask"].to(device) + model_inputs = {key: value.to(device) for key, value in tokens.items()} + text = bert(**model_inputs).last_hidden_state[0].float().cpu().numpy() + text_mask = attention[0].bool().cpu().numpy() + text[~text_mask] = 0.0 + audio, audio_length = _fixed_source_rows(raw["audio"], 74, "audio") + vision, vision_length = _fixed_source_rows(raw["vision"], 35, "vision") + token_types = tokens.get("token_type_ids", torch.zeros_like(ids))[0].cpu().numpy() + text_bert = np.stack((ids[0].cpu().numpy(), text_mask.astype(np.int64), token_types), axis=0)[None] + split = {"id": np.asarray([case_id]), "text_bert": text_bert, + "text": text[None].astype(np.float32), "audio": audio[None], "vision": vision[None], + "audio_lengths": np.asarray([max(1, audio_length)]), + "vision_lengths": np.asarray([max(1, vision_length)])} + from ...adapter import adapt_official_split + projected, projected_mask, adapter_audit = adapt_official_split(split) + case = {name: projected[name][0] for name in MODALITIES} + case["text_mask"] = projected_mask[0, :, 0] + case_id = _decode_attachment_text(raw.get("id", case_id)) or case_id + input_note = ("unaligned features projected by the shared Q1 adapter; raw_text re-encoded with BERT; " + "audio/vision lengths inferred from last nonzero row because attachment 3 omits trusted lengths") + else: + loaded = load_attachment3_case(path) + ids = torch.from_numpy(loaded["input_ids"][None]).to(device) + attention = torch.from_numpy(loaded["attention_mask"][None].astype(np.int64)).to(device) + segments = torch.from_numpy(loaded["token_type_ids"][None]).to(device) + text = bert(input_ids=ids, attention_mask=attention, token_type_ids=segments).last_hidden_state[0].float().cpu().numpy() + text_mask = loaded["attention_mask"] + text[~text_mask] = 0.0 + case = {"text": text, "audio": loaded["audio"], "vision": loaded["vision"], "text_mask": text_mask} + input_note = "official aligned_50 features; text re-encoded from supplied BERT token IDs" + cases.append({"case_id": case_id, **case}) + audio_mask = np.any(case["audio"] != 0, axis=1) + vision_mask = np.any(case["vision"] != 0, axis=1) + audit.append({"case_id": case_id, "source_file": path.name, + "text_visible_steps": int(case["text_mask"].sum()), + "audio_visible_steps": int(audio_mask.sum()), "vision_visible_steps": int(vision_mask.sum()), + "audio_missing_fraction": float(1.0 - audio_mask.mean()), + "vision_missing_fraction": float(1.0 - vision_mask.mean()), + "unknown_quality_flag": True, "labels_available": False, + "input_note": input_note, + "source_coordinate_mode": "relative_progress" if input_version == "unaligned_50" else "aligned_50", + "source_sha256": sha256(path)}) + del bert + return cases, audit + + +def infer_attachment3( + model: CRG, + cases: list[dict[str, Any]], + fitted: dict[str, dict[str, np.ndarray]], + device: torch.device, + temperature: float, + prior_probs: np.ndarray, + magnitude_priors: np.ndarray, +) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + model.eval() + predictions, audit = [], [] + for case in cases: + mask = np.stack((case["text_mask"], np.any(case["audio"] != 0, axis=1), np.any(case["vision"] != 0, axis=1)), axis=-1)[None] + xs_np = {} + for modality in MODALITIES: + values = case[modality].astype(np.float32) + values = np.clip((values - fitted[modality]["mean"]) / fitted[modality]["std"], -10.0, 10.0) + values[~mask[0, :, MODALITIES.index(modality)]] = 0.0 + xs_np[modality] = values[None] + low_information = not bool(mask.any()) + if low_information: + p = np.asarray(prior_probs, dtype=np.float64) + p = p / p.sum() + max_probability = float(p.max()) + predicted_class = next(c for c in (1, 0, 2) if math.isclose(float(p[c]), max_probability, rel_tol=0.0, abs_tol=1e-12)) + score = 0.0 + beta = np.full((1, 1, 2, 2), np.nan, np.float32) + if predicted_class != 1: + sign_index = 0 if predicted_class == 0 else 1 + magnitude = float(betaincinv(magnitude_priors[sign_index, 0], magnitude_priors[sign_index, 1], 0.5)) + score = (-3.0 if predicted_class == 0 else 3.0) * magnitude + beta[0, 0] = magnitude_priors + ppaths = p[None, None, :] + else: + arrays = {m: xs_np[m] for m in MODALITIES} + xs, observed = to_device_batch(arrays, mask, np.asarray([0]), device) + with torch.inference_mode(): + out = model(xs, observed, paths=16 if model.use_joint_draws else 1, joint_draws=model.use_joint_draws) + ppaths = out["class_probs_by_path"].cpu().numpy()[:, 0:1] + beta = out["beta_params"].cpu().numpy()[:, 0:1] + p, classes, scores = _decode_mixture(ppaths, beta, temperature) + p, predicted_class, score = p[0], int(classes[0]), float(scores[0]) + interval_temperature = 1.0 if low_information else temperature + interval_lower, interval_upper = _predictive_intervals(ppaths, beta, interval_temperature) + variance_components = _trajectory_variance_components(ppaths, beta) + calibrated_moments = _calibrated_mixture_moments(ppaths, beta, interval_temperature) + row = {"case_id": case["case_id"], "predicted_class": predicted_class, + "predicted_class_name": ("negative", "neutral", "positive")[predicted_class], + "predicted_sentiment": float(score), "p_negative": float(p[0]), + "p_neutral": float(p[1]), "p_positive": float(p[2]), + "interval_90_lower": float(interval_lower[0]), "interval_90_upper": float(interval_upper[0]), + "predictive_variance_mean_uncalibrated": float(variance_components[0][0]), + "within_trajectory_variance": float(variance_components[1][0]), + "between_trajectory_variance": float(variance_components[2][0]), + "predictive_mean_calibrated": float(calibrated_moments[0][0]), + "predictive_variance_calibrated": float(calibrated_moments[1][0]), + "beta_negative_alpha": float(beta[0, 0, 0, 0]), "beta_negative_beta": float(beta[0, 0, 0, 1]), + "beta_positive_alpha": float(beta[0, 0, 1, 0]), "beta_positive_beta": float(beta[0, 0, 1, 1]), + "low_information_prior_fallback": low_information, + "output_note": "unlabeled attachment-3 case; no accuracy/F1 is defined"} + predictions.append(row) + audit.append({"case_id": case["case_id"], "visible_text_steps": int(mask[0, :, 0].sum()), + "visible_audio_steps": int(mask[0, :, 1].sum()), "visible_vision_steps": int(mask[0, :, 2].sum()), + "low_information_prior_fallback": low_information, + "calibration_temperature": interval_temperature, + "interval_90_lower": float(interval_lower[0]), "interval_90_upper": float(interval_upper[0]), + "predictive_variance_mean_uncalibrated": float(variance_components[0][0]), + "predictive_variance_calibrated": float(calibrated_moments[1][0])}) + return predictions, audit + + +def validate_attachment3_predictions(expected_ids: list[str], predictions: list[dict[str, Any]]) -> None: + """Check the unlabeled submission contract and sign/strength consistency.""" + predicted_ids = [str(row["case_id"]) for row in predictions] + if len(predictions) != len(expected_ids) or len(set(expected_ids)) != len(expected_ids): + raise ValueError("attachment-3 prediction count differs from unique expected IDs") + if len(set(predicted_ids)) != len(predicted_ids) or set(predicted_ids) != set(expected_ids): + raise ValueError("attachment-3 predictions must contain every expected ID exactly once") + for row in predictions: + predicted_class = int(row["predicted_class"]) + score = float(row["predicted_sentiment"]) + probabilities = np.asarray([row["p_negative"], row["p_neutral"], row["p_positive"]], dtype=np.float64) + if predicted_class not in (0, 1, 2) or not np.isfinite(score) or not -3.0 <= score <= 3.0: + raise ValueError(f"invalid attachment-3 class/strength for {row['case_id']}") + if (predicted_class == 1 and score != 0.0) or (predicted_class == 0 and not score < 0.0) or ( + predicted_class == 2 and not score > 0.0 + ): + raise ValueError(f"attachment-3 class/strength polarity mismatch for {row['case_id']}") + if not np.isfinite(probabilities).all() or np.any(probabilities < 0.0) or not np.isclose(probabilities.sum(), 1.0, atol=1e-6): + raise ValueError(f"invalid attachment-3 class probabilities for {row['case_id']}") + low, high = float(row["interval_90_lower"]), float(row["interval_90_upper"]) + if not np.isfinite([low, high]).all() or low > high or low < -3.0 or high > 3.0: + raise ValueError(f"invalid attachment-3 prediction interval for {row['case_id']}") + + +def main() -> None: + global DELTA_U, RESULTS + parser = argparse.ArgumentParser() + parser.add_argument("--input-version", choices=("aligned_50", "unaligned_50"), default="unaligned_50") + parser.add_argument("--output-dir", type=Path, default=None, + help="Write this run to a new directory instead of the default results directory") + parser.add_argument("--epochs", type=int, default=12) + parser.add_argument("--imputer-epochs", type=int, default=8) + parser.add_argument("--batch-size", type=int, default=64) + parser.add_argument("--patience", type=int, default=3) + parser.add_argument("--seed", type=int, default=SEED) + parser.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu") + parser.add_argument("--bootstrap-repeats", type=int, default=1000) + parser.add_argument("--skip-attachment3", action="store_true") + args = parser.parse_args() + RESULTS = args.output_dir or Q2_DIR / ("results_unaligned" if args.input_version == "unaligned_50" else "results") + if args.output_dir is not None and RESULTS.exists() and any(RESULTS.iterdir()): + parser.error(f"refusing to overwrite non-empty result directory: {RESULTS}") + seed_everything(args.seed) + rng = np.random.default_rng(args.seed) + device = torch.device(args.device) + RESULTS.mkdir(parents=True, exist_ok=True) + if device.type == "cuda": + print(f"device={device} ({torch.cuda.get_device_name(device)})", flush=True) + + input_path = ATTACHMENT2_DIR / f"{args.input_version}.pkl" + official = load_official_splits(input_path, version=args.input_version) + overlaps = assert_group_disjoint(official) + fit, heldout_train = split_calibration(official["train"], args.seed) + reliability_validation, temperature_calibration = split_calibration(heldout_train, args.seed + 1, fraction=0.5) + reliability_validation.name = "reliability_validation" + temperature_calibration.name = "temperature_calibration" + if (set(fit.groups) & set(reliability_validation.groups) + or set(fit.groups) & set(temperature_calibration.groups) + or set(reliability_validation.groups) & set(temperature_calibration.groups)): + raise AssertionError("fit, reliability-selection, and temperature-calibration videos must be disjoint") + DELTA_U = label_resolution_from_train(fit.regression_y) + magnitude_priors = fit_magnitude_priors(fit.regression_y) + class_counts_fit = np.bincount(fit.class_y, minlength=3).astype(np.float64) + class_prior_probs = (class_counts_fit + 1.0) / (class_counts_fit.sum() + 3.0) + # Freeze the exact validation masks before fitting any model. + validation_scenarios = make_scenarios(official["valid"], args.seed + 909) + reliability_scenarios = make_reliability_scenarios(reliability_validation, args.seed + 906) + print("official splits:", {k: (v.n, len(np.unique(v.groups))) for k, v in official.items()}, + "fit/reliability_validation/temperature_calibration:", + (fit.n, reliability_validation.n, temperature_calibration.n), "group_overlap:", overlaps, flush=True) + fitted = fit_preprocessor(fit) + transformed = {name: transform_split(split, fitted) for name, split in official.items()} + transformed["fit"] = transform_split(fit, fitted) + transformed["reliability_validation"] = transform_split(reliability_validation, fitted) + transformed["temperature_calibration"] = transform_split(temperature_calibration, fitted) + np.savez_compressed(RESULTS / "preprocessor.npz", **{f"{m}_{k}": v for m, stats in fitted.items() for k, v in stats.items()}) + + imputer = StructuredGaussianImputer(INPUT_DIMS).to(device) + imputer_history = fit_imputer(imputer, transformed["fit"], fit, device, args.imputer_epochs, args.batch_size, args.seed + 1) + torch.save({k: v.detach().cpu() for k, v in imputer.state_dict().items()}, RESULTS / "structured_imputer.pt") + + teacher = _make_variant("C6", imputer).to(device) + teacher, history_teacher = _fit_neural( + teacher, "teacher", fit, official["valid"], transformed, device, args.epochs, + args.batch_size, args.patience, np.random.default_rng(args.seed + 2), + selection_split=reliability_validation, selection_arrays=transformed["reliability_validation"], + selection_scenarios={"0.0/natural": reliability_validation.mask.copy()}, + ) + teacher_reliability, teacher_tuning_rows = tune_reliability_hparams( + teacher, transformed["reliability_validation"], reliability_validation, reliability_scenarios, + device, args.batch_size, "teacher", seed=args.seed + 551, + ) + torch.save({k: v.detach().cpu() for k, v in teacher.state_dict().items()}, RESULTS / "teacher.pt") + + ablation_rows: list[dict[str, Any]] = [] + history = list(imputer_history) + history_teacher + reliability_tuning_rows = list(teacher_tuning_rows) + group_risk_tuning_rows: list[dict[str, Any]] = [] + models: dict[str, CRG] = {} + _, c0_state = fit_c0(fit, official["valid"], transformed) + calibrate_c0_interval(c0_state, temperature_calibration, transformed["temperature_calibration"]) + _, c0_cal_pred = evaluate_c0(c0_state, temperature_calibration, transformed["temperature_calibration"]) + c0_temperature = fit_temperature(c0_cal_pred["probabilities"], temperature_calibration.class_y) + c0_metrics, c0_valid_pred = evaluate_c0(c0_state, official["valid"], transformed["valid"], c0_temperature) + c0_metrics["temperature"] = c0_temperature + ablation_rows.append({"model": "C0", **c0_metrics, "description": "observed mean/std + masks + maximum gap; logistic/ridge"}) + best_model_name: str | None = None + best_valid_loss = float("inf") + temperatures = {"C0": c0_temperature} + selected_group_risk: dict[str, tuple[float, float]] = {} + validation_predictions: dict[str, dict[str, np.ndarray]] = {"C0": c0_valid_pred} + + definitions = { + "C1": "masked BiGRU; no posterior imputation, explicit reliability or source gate", + "C2": "exact Gaussian posterior mean; no joint trajectory integral", + "C3": "joint trajectory integral plus final reliability/content fusion gate", + "C4": "C3 plus bounded cross-time source attention and null source", + "C5": "C4 plus reliability-modulated BiGRU update", + "C6": "C5 plus optional rank-4 CP residual", + "C6_no_distance": "C6 with uncertainty retained but both distance/span reliability penalties fixed to zero", + "C6_no_reconstruction": "C6 trained without the auxiliary hidden-feature reconstruction loss", + "C6_pointmask": "C6 trained with independent point masking instead of contiguous spans", + "C7_distill": "C6 plus entropy/retention-weighted teacher distillation only", + "C7_group": "C6 plus smooth worst-group risk only", + } + diagnostic_ablation_names = {"C6_no_distance", "C6_no_reconstruction", "C6_pointmask"} + model_names = ("C1", "C2", "C3", "C4", "C5", "C6", *sorted(diagnostic_ablation_names), "C7_distill", "C7_group") + no_distance_candidates = tuple(value for value in RELIABILITY_CANDIDATES if value[2] == 0.0 and value[3] == 0.0) + for name in model_names: + if name == "C7_group": + (variant, rows, reliability_rows, risk_rows, selected_reliability, + selected_risk, _) = tune_group_risk_model( + imputer, fit, official["valid"], transformed, + reliability_validation, transformed["reliability_validation"], reliability_scenarios, + device, args.epochs, args.batch_size, args.patience, args.seed + 303, + ) + reliability_tuning_rows.extend(reliability_rows) + group_risk_tuning_rows.extend(risk_rows) + selected_group_risk[name] = selected_risk + else: + base_name = "C6" if name in diagnostic_ablation_names else name + variant = _make_variant(base_name, imputer) + if name == "C6_no_distance": + _set_reliability_hparams(variant, (DEFAULT_RELIABILITY[0], DEFAULT_RELIABILITY[1], 0.0, 0.0)) + kd_teacher = teacher if name == "C7_distill" else None + mask_kind = "point" if name == "C6_pointmask" else "continuous" + variant, rows = _fit_neural( + variant, name, fit, official["valid"], transformed, device, args.epochs, + args.batch_size, args.patience, np.random.default_rng(args.seed + 303), + teacher=kd_teacher, + selection_split=reliability_validation, + selection_arrays=transformed["reliability_validation"], + selection_scenarios=reliability_scenarios, + mask_kind=mask_kind, + use_reconstruction=name != "C6_no_reconstruction", + ) + selected_reliability, tuning_rows = tune_reliability_hparams( + variant, transformed["reliability_validation"], reliability_validation, reliability_scenarios, + device, args.batch_size, name, seed=args.seed + 551, + candidate_values=no_distance_candidates if name == "C6_no_distance" else RELIABILITY_CANDIDATES, + ) + reliability_tuning_rows.extend(tuning_rows) + selected_group_risk[name] = (float("nan"), float("nan")) + history.extend(rows) + models[name] = variant + _, calibration_pred = evaluate(variant, transformed["temperature_calibration"], temperature_calibration, + device, args.batch_size) + model_temperature = fit_temperature(calibration_pred["probabilities"], temperature_calibration.class_y) + temperatures[name] = model_temperature + raw_metrics, _ = evaluate(variant, transformed["valid"], official["valid"], device, args.batch_size) + metrics, valid_prediction = evaluate(variant, transformed["valid"], official["valid"], device, args.batch_size, + temperature=model_temperature) + validation_predictions[name] = valid_prediction + metrics["validation_selection_loss"] = raw_metrics["selection_nll"] + metrics["temperature"] = model_temperature + metrics.update({"rho_imp": selected_reliability[0], "lambda_u": selected_reliability[1], + "lambda_gap": selected_reliability[2], "lambda_span": selected_reliability[3]}) + metrics["lambda_group"], metrics["group_temperature"] = selected_group_risk[name] + ablation_rows.append({"model": name, **metrics, "description": definitions[name]}) + if name not in diagnostic_ablation_names and raw_metrics["selection_nll"] < best_valid_loss: + best_valid_loss, best_model_name = raw_metrics["selection_nll"], name + write_csv(RESULTS / "ablation_validation.csv", ablation_rows) + write_csv(RESULTS / "reliability_hparam_tuning.csv", reliability_tuning_rows) + write_csv(RESULTS / "group_risk_tuning.csv", group_risk_tuning_rows) + write_csv(RESULTS / "validation_group_bootstrap_deltas.csv", + paired_group_bootstrap_deltas(official["valid"], validation_predictions, args.bootstrap_repeats, args.seed + 88)) + if best_model_name is None: + raise RuntimeError("no neural ablation candidate completed") + best_model = models[best_model_name] + + # Calibration is on a group-held-out slice of official training data, never on official test. + temperature = temperatures[best_model_name] + valid_metrics, valid_pred = evaluate(best_model, transformed["valid"], official["valid"], device, args.batch_size, temperature=temperature) + test_metrics, test_pred = evaluate( + best_model, transformed["test"], official["test"], device, args.batch_size, + temperature=temperature, collect_gate_diagnostics=True, + ) + torch.save({k: v.detach().cpu() for k, v in best_model.state_dict().items()}, RESULTS / "crg_student.pt") + (RESULTS / "validation_metrics.json").write_text(json.dumps({**valid_metrics, "selected_model": best_model_name, "temperature": temperature}, indent=2), encoding="utf-8") + (RESULTS / "test_metrics.json").write_text(json.dumps({**test_metrics, "selected_model": best_model_name, "temperature": temperature}, indent=2), encoding="utf-8") + + test_rows = [] + for i, sample_id in enumerate(official["test"].ids): + p = test_pred["probabilities"][i] + test_rows.append({"sample_id": sample_id, "source_video_id": official["test"].groups[i], + "true_class": int(official["test"].class_y[i]), "predicted_class": int(test_pred["predicted_class"][i]), + "true_sentiment": float(official["test"].regression_y[i]), "predicted_sentiment": float(test_pred["predicted_score"][i]), + "p_negative": float(p[0]), "p_neutral": float(p[1]), "p_positive": float(p[2]), + "interval_90_lower": float(test_pred["interval_lower"][i]), + "interval_90_upper": float(test_pred["interval_upper"][i]), + "predictive_variance_mean_uncalibrated": float(test_pred["predictive_variance_uncalibrated"][i]), + "within_trajectory_variance": float(test_pred["within_trajectory_variance"][i]), + "between_trajectory_variance": float(test_pred["between_trajectory_variance"][i])}) + write_csv(RESULTS / "test_predictions.csv", test_rows) + write_csv(RESULTS / "test_gate_diagnostics.csv", gate_diagnostic_rows(official["test"], test_pred)) + write_csv(RESULTS / "group_bootstrap_ci.csv", + group_bootstrap(official["test"], test_pred, args.bootstrap_repeats, args.seed + 44)) + + write_csv(RESULTS / "controlled_mask_audit.csv", + mask_audit_rows(official["valid"], validation_scenarios, args.seed + 909)) + controlled_rows, c0_scenario_predictions = controlled_c0( + c0_state, official["valid"], transformed["valid"], validation_scenarios, c0_temperature, + ) + controlled_predictions: dict[str, dict[str, dict[str, np.ndarray]]] = {"C0": c0_scenario_predictions} + for name, candidate in models.items(): + candidate_rows, candidate_predictions = controlled_metrics( + candidate, official["valid"], transformed["valid"], validation_scenarios, + device, args.batch_size, name, temperatures[name], seed=args.seed + 552, + ) + controlled_rows.extend(candidate_rows) + controlled_predictions[name] = candidate_predictions + write_csv(RESULTS / "controlled_missingness.csv", controlled_rows) + write_csv(RESULTS / "controlled_group_bootstrap.csv", + controlled_group_bootstrap(official["valid"], controlled_predictions, validation_scenarios, + args.bootstrap_repeats, args.seed + 553)) + write_csv(RESULTS / "training_history.csv", history) + + attachment_count = 0 + if not args.skip_attachment3: + cases, attachment_audit = reencode_attachment3(device, args.input_version) + attachment_predictions, inference_audit = infer_attachment3(best_model, cases, fitted, device, temperature, class_prior_probs, magnitude_priors) + validate_attachment3_predictions([case["case_id"] for case in cases], attachment_predictions) + write_csv(RESULTS / "attachment3_predictions.csv", attachment_predictions) + write_csv(RESULTS / "attachment3_audit.csv", [dict(a, **next(x for x in inference_audit if x["case_id"] == a["case_id"])) for a in attachment_audit]) + attachment_count = len(cases) + + manifest = { + "seed": args.seed, + "text_encoder": ("official precomputed text field; encoder revision not supplied" + if args.input_version == "unaligned_50" else TEXT_MODEL_ID), + "training_configuration": {"student_epoch_limit": args.epochs, "imputer_epochs": args.imputer_epochs, + "batch_size": args.batch_size, "early_stopping_patience": args.patience, + "device": str(device), + "device_name": torch.cuda.get_device_name(device) if device.type == "cuda" else "CPU", + "optimizer": "AdamW", "student_learning_rate": 3e-4, + "student_weight_decay": 1e-3, "imputer_learning_rate": 3e-4, + "imputer_weight_decay": 1e-4, + "early_stopping_metric": "mean untempered selection_nll over fixed group-disjoint internal training scenarios", + "inner_selection_scenarios": list(reliability_scenarios), + "inner_selection_source_video_groups": int(len(np.unique(reliability_validation.groups)))}, + "training_input": str(input_path.relative_to(DATA_ROOT)) if input_path.is_relative_to(DATA_ROOT) else str(input_path), + "input_version": args.input_version, + "q1_alignment_adapter": {name: split.alignment_audit for name, split in official.items()} + if args.input_version == "unaligned_50" else None, + "training_sha256": sha256(input_path), + "official_group_overlap": overlaps, + "official_splits": {name: {"n": split.n, "source_video_groups": int(len(np.unique(split.groups)))} for name, split in official.items()}, + "internal_train_holdouts": { + "fit": {"n": fit.n, "video_groups": int(len(np.unique(fit.groups)))}, + "reliability_selection": {"n": reliability_validation.n, "video_groups": int(len(np.unique(reliability_validation.groups)))}, + "temperature_calibration": {"n": temperature_calibration.n, "video_groups": int(len(np.unique(temperature_calibration.groups)))}, + "all_group_disjoint": True, + }, + "feature_standardization": "fit-only observed rows, per-dimension; fixed for valid/test/attachment3", + "missing_mask": ("official text attention and source lengths plus row observation; normalized-progress overlap preserves empty bins; q*=1 only where visible, J_Q=0" + if args.input_version == "unaligned_50" else + "row-level all-zero convention; q*=1 only where currently visible, J_Q=0; hidden metadata is zeroed"), + "observation_quality": {"quality_score_fields_present": False, "quality_available_flag_present": False, + "fallback": "q*=1 and J_Q=0 for visible rows; R_eff=R", + "quality_noise_mapping_ablation": f"not identifiable on {args.input_version} because no row quality score varies"}, + "imputer": {"type": "structured linear Gaussian shared-private state space", "state_dims": {"shared": 8, "private_each": 4}, + "posterior": "block-tridiagonal equivalent Kalman information filter + RTS smoother", + "sampling": "joint latent trajectories and missing emissions; observed features copied exactly", + "fit_objective": "train-only observed Gaussian marginal likelihood including log determinants", + "epochs": args.imputer_epochs, "frozen_before_teacher_student": True}, + "architecture": {"projection": 32, "bigru_hidden_each_direction": 16, "cross_source_layers": 1, + "cross_time_read": True, "rank": 4, "reliability_gru": "directional hidden decay; reset applied before candidate map; update gate multiplied by rho", + "final_gate": "rho times bounded content score plus positive null prior", + "output": "neutral point mass plus sign-specific Beta magnitudes; K-path probabilities mixed before decoding"}, + "reliability_hyperparameters": {"selected_per_model_on": "group-disjoint internal training reliability-validation slice", + "candidate_values": [list(v) for v in RELIABILITY_CANDIDATES], + "validation_scenarios": list(reliability_scenarios), + "selected_by_model": {row["model"]: [row["rho_imp"], row["lambda_u"], row["lambda_gap"], row["lambda_span"]] + for row in reliability_tuning_rows if row.get("selected")}}, + "group_risk_hyperparameters": { + "selection": "lambda_group and group_temperature jointly selected with reliability hyperparameters on fixed group-disjoint internal training scenarios", + "candidate_values": [list(v) for v in GROUP_RISK_CANDIDATES], + "selected": list(selected_group_risk["C7_group"]), + "selection_split": reliability_validation.name, + }, + "loss": {"supervision": "negative log mixture of Beta interval masses plus scaled Huber mean term", + "delta_u": DELTA_U, "delta_u_source": "half the minimum positive spacing of nonzero absolute labels in fit only", + "lambda_y": LAMBDA_Y, "lambda_distill": LAMBDA_DISTILL, + "lambda_reconstruction": LAMBDA_RECON, + "lambda_group_default": LAMBDA_GROUP, "group_temperature_default": GROUP_TEMPERATURE, + "selected_group_risk": list(selected_group_risk["C7_group"]), + "distill_temperature": DISTILL_TEMPERATURE, "distill_retention_exponent": 1.0, + "imputer_regularization": {"emission_l2": LAMBDA_EMISSION, "transition_l2": LAMBDA_TRANSITION}, + "group_and_distill_separate": True}, + "calibration": {"method": "temperature scaling on a group-disjoint internal official-train holdout, separated from reliability selection", + "temperature": temperature, "valid_used_for_selection": True, "test_used_for_selection_or_calibration": False}, + "selected_model": best_model_name, + "attachment3_low_information_priors": {"class_probability_method": "fit counts + one pseudocount per class", + "class_probability_values": class_prior_probs.tolist(), + "negative_beta": magnitude_priors[0].tolist(), + "positive_beta": magnitude_priors[1].tolist()}, + "ablation_definitions": definitions, + "masking": {"rates": MASK_RATES, "patterns": list(MASK_MODES), "preserve_at_least_fraction_per_selected_modality": 0.2, + "controlled_sweep_split": "official validation", "identical_masks_across_models": True, + "scenario_count": len(validation_scenarios), "scenario_seed": args.seed + 909, + "training_mask_rng_seed": args.seed + 303, + "reliability_scenario_seed": args.seed + 906, + "controlled_torch_sampling_seed": args.seed + 552, + "paired_control_bootstrap_seed": args.seed + 553, + "mask_audit_file": "controlled_mask_audit.csv", + "additional_one_factor_controls": ["modality T/A/V and combinations", "start/middle/end", "one-long/multiple-short", "sync/partial/async"], + "semantic_position_control": f"not run: {args.input_version} does not provide audited semantic boundary indices; raw text is prohibited in student inputs"}, + "final_test_metrics": test_metrics, + "test_gate_diagnostics_file": "test_gate_diagnostics.csv", + "attachment3_cases": attachment_count, + "attachment3_labeled_metrics": None, + "completed_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + } + (RESULTS / "run_manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8") + print("Q2 complete:", json.dumps({"selected_model": best_model_name, "test_accuracy": test_metrics["accuracy"], + "test_macro_f1": test_metrics["macro_f1"], "test_mae": test_metrics["regression_mae"], + "temperature": temperature, "attachment3_cases": attachment_count}, ensure_ascii=False), flush=True) + + +if __name__ == "__main__": + main() diff --git a/submit/final/q3/__init__.py b/submit/final/q3/__init__.py new file mode 100644 index 0000000..3964999 --- /dev/null +++ b/submit/final/q3/__init__.py @@ -0,0 +1 @@ +"""Q3 interpretable emotion-recognition pipeline.""" diff --git a/submit/final/q3/ati_ho/README.md b/submit/final/q3/ati_ho/README.md new file mode 100644 index 0000000..f52c9b3 --- /dev/null +++ b/submit/final/q3/ati_ho/README.md @@ -0,0 +1,46 @@ +# ATI–HO 训练与评估 + +ATI–HO 是当前 Q3 方案。模型定义位于 `final/model/ati_ho.py` 和 `final/model/ati_ho_config.py`。 + +## 输入准备 + +设置 `FINAL_DATA_DIR` 指向官方数据根目录。完整训练和评估需要附件 2 的 `unaligned_50.pkl`、附件 4 的未对齐特征文件和训练集 robust scaler: + +`final/experiments/q2/unaligned_deep_two_b128/unaligned_50_robust_stats.npz` + +评估时附件 4 视频文件仅用于来源核验,不是模型输入。模型只读取特征文件。所有未对齐输入由统一 adapter 投影到 50 个 Relative-Progress 槽,不能解释为物理时间同步。 + +## 训练命令 + +从仓库根目录执行: + +```bash +export FINAL_DATA_DIR="/path/to/E题数据" +python -m final.q3.ati_ho.train --phase all --device auto +``` + +`--phase stage1` 运行 seed 42 的 A0/A1/A2/A3 和 D0 结构审计;`--phase stage2` 使用 Stage I 写出的 provisional candidate 运行三 seed 基线和关键消融。默认最多 12 轮,固定验证情景任务损失早停。训练记录与检查点保存在 `final/experiments/q3/ati_ho/`。只有明确要覆盖检查点时才加 `--force`。 + +## 评估命令 + +```bash +export FINAL_DATA_DIR="/path/to/E题数据" +python -m final.q3.ati_ho.evaluate --device auto +``` + +评估会比较 ATI 消融、重算官方验证指标和按来源视频组 Bootstrap、审计解析/精确 Shapley、运行附件 4 局部 Owen 和删除/保留诊断,并生成 Q3 输出。最终 ATI 方案按三 seed、四个固定验证情景的平均任务损失选出。附件 4 标签不会读取或用于报告;附件 4 输出没有准确率。 + +若完整评估已写完 CSV,但报告阶段中断,可运行: + +```bash +python -m final.q3.ati_ho.evaluate --reports-only +``` + +若只需补算训练 seed 与 1% 输入扰动下的 attribution 稳定性: + +```bash +export FINAL_DATA_DIR="/path/to/E题数据" +python -m final.q3.ati_ho.evaluate --device auto --stability-only +``` + +题目交付文件写入 `final/output/q3/ati_ho/`。完整结果表、审计和论文式记录位于 `final/experiments/q3/ati_ho/results/ati_ho/`。验证指标解释和结果边界见 `final/REPORTS.md`。 diff --git a/submit/final/q3/ati_ho/__init__.py b/submit/final/q3/ati_ho/__init__.py new file mode 100644 index 0000000..a6ff6f1 --- /dev/null +++ b/submit/final/q3/ati_ho/__init__.py @@ -0,0 +1,6 @@ +"""ATI–HO: anchored temporal interactions with hierarchical Owen attribution.""" + +from ...model.ati_ho import ATIHOModel +from ...model.ati_ho_config import ATIConfig, CONFIGS + +__all__ = ["ATIConfig", "CONFIGS", "ATIHOModel"] diff --git a/submit/final/q3/ati_ho/attribution.py b/submit/final/q3/ati_ho/attribution.py new file mode 100644 index 0000000..105fb5e --- /dev/null +++ b/submit/final/q3/ati_ho/attribution.py @@ -0,0 +1,166 @@ +from __future__ import annotations + +import itertools +import math +from typing import Any, Sequence + +import numpy as np +import torch +from torch import nn + + +def ensemble_forward( + models: Sequence[nn.Module], + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + masks: torch.Tensor, + *, + details: bool = True, +) -> dict[str, Any]: + """Average additive parameters first, then decode the ensemble prediction.""" + outputs = [] + for model in models: + try: + outputs.append(model(xs, masks, return_details=details)) + except TypeError: + outputs.append(model(xs, masks)) + if "params" not in outputs[0]: + logits = torch.stack([output["logits"] for output in outputs], dim=0).mean(dim=0) + probabilities = torch.stack( + [torch.softmax(output["logits"], dim=-1) for output in outputs], dim=0 + ).mean(dim=0) + intensity = torch.stack([output["intensity"] for output in outputs], dim=0).mean(dim=0) + result = { + "logits": logits, + "probabilities": probabilities, + "predicted_class": logits.argmax(dim=-1), + "intensity": intensity, + } + if "utility" in outputs[0]: + result["utility"] = torch.stack( + [output["utility"] for output in outputs], dim=0 + ).mean(dim=0) + return result + result: dict[str, Any] = {} + averaged = ("params", "baseline", "main_effects", "pair_effects", "mask_logits") + for key in averaged: + if key in outputs[0]: + result[key] = torch.stack([output[key] for output in outputs], dim=0).mean(dim=0) + params = result["params"] + logits = params[:, :3] + probabilities = torch.softmax(logits, dim=-1) + nu_negative = 3.0 * torch.sigmoid(params[:, 3]) + nu_positive = 3.0 * torch.sigmoid(params[:, 4]) + predicted_class = logits.argmax(dim=-1) + intensity = torch.where( + predicted_class == 0, + -nu_negative, + torch.where(predicted_class == 2, nu_positive, torch.zeros_like(nu_positive)), + ) + result.update( + { + "logits": logits, + "probabilities": probabilities, + "predicted_class": predicted_class, + "intensity": intensity, + "nu_negative": nu_negative, + "nu_positive": nu_positive, + "soft_intensity": probabilities[:, 2] * nu_positive - probabilities[:, 0] * nu_negative, + } + ) + return result + + +def shapley_from_eight(values: np.ndarray) -> np.ndarray: + """Exact three-player Shapley values from the eight coalition values.""" + values = np.asarray(values, dtype=np.float64) + if values.shape[-1] != 8: + raise ValueError("the three-modality game requires exactly eight coalition values") + result = np.zeros((*values.shape[:-1], 3), dtype=np.float64) + factorial = math.factorial + for modality in range(3): + others = [i for i in range(3) if i != modality] + for size in range(3): + weight = factorial(size) * factorial(2 - size) / factorial(3) + for subset in itertools.combinations(others, size): + before = sum(1 << item for item in subset) + after = before | (1 << modality) + result[..., modality] += weight * (values[..., after] - values[..., before]) + return result + + +def analytic_class_shapley(details: dict[str, torch.Tensor], target: torch.Tensor, other: torch.Tensor) -> np.ndarray: + """Closed form for the fixed target-vs-runner-up logit margin.""" + main = details["main_effects"] + pairs = details["pair_effects"] + delta = torch.zeros((main.shape[0], 3), dtype=main.dtype, device=main.device) + rows = torch.arange(main.shape[0], device=main.device) + delta[rows, target] = 1.0 + delta[rows, other] = -1.0 + contributions = main.clone() + pair_modalities = ((0, 1), (0, 2), (1, 2)) + for pair_idx, (left, right) in enumerate(pair_modalities): + contributions[:, left] = contributions[:, left] + 0.5 * pairs[:, pair_idx] + contributions[:, right] = contributions[:, right] + 0.5 * pairs[:, pair_idx] + values = torch.einsum("bi,bmi->bm", delta, contributions[..., :3]) + return values.detach().cpu().numpy().astype(np.float64) + + +@torch.inference_mode() +def exact_shapley_audit( + models: Sequence[nn.Module], + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + masks: torch.Tensor, + *, + batch_size: int = 128, +) -> dict[str, np.ndarray]: + """Compare analytic output-parameter Shapley with exact 8-coalition values. + + The class target and runner-up are fixed from each sample's full-input + prediction. A second exact game is computed for the decoded hard intensity; + that value is nonlinear and is not compared with the analytic formula. + """ + n = masks.shape[0] + full = ensemble_forward(models, xs, masks, details=True) + logits = full["logits"] + target = logits.argmax(dim=-1) + ranked = logits.argsort(dim=-1, descending=True) + other = ranked[:, 1] + analytic = analytic_class_shapley(full, target, other) + + margin_values = np.zeros((n, 8), dtype=np.float64) + intensity_values = np.zeros((n, 8), dtype=np.float64) + for coalition in range(8): + for start in range(0, n, batch_size): + end = min(n, start + batch_size) + current_mask = masks[start:end].clone() + for modality in range(3): + if not coalition & (1 << modality): + current_mask[..., modality] = False + current_xs = tuple(x[start:end] for x in xs) + output = ensemble_forward(models, current_xs, current_mask, details=False) + local_rows = torch.arange(end - start, device=logits.device) + local_target = target[start:end] + local_other = other[start:end] + margin = ( + output["logits"][local_rows, local_target] + - output["logits"][local_rows, local_other] + ) + margin_values[start:end, coalition] = margin.detach().cpu().numpy() + intensity_values[start:end, coalition] = output["intensity"].detach().cpu().numpy() + + exact = shapley_from_eight(margin_values) + exact_intensity = shapley_from_eight(intensity_values) + error = np.abs(analytic - exact) + tolerance = 1e-6 + 1e-5 * np.abs(exact) + return { + "analytic_class": analytic, + "exact_class": exact, + "class_abs_error": error, + "class_pass": error <= tolerance, + "exact_intensity": exact_intensity, + "coalition_margin": margin_values, + "coalition_intensity": intensity_values, + "target_class": target.detach().cpu().numpy(), + "runner_up_class": other.detach().cpu().numpy(), + "full_output": full, + } diff --git a/submit/final/q3/ati_ho/audit.py b/submit/final/q3/ati_ho/audit.py new file mode 100644 index 0000000..86a856a --- /dev/null +++ b/submit/final/q3/ati_ho/audit.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from typing import Any + +import torch +from torch import nn + + +PAIR_MODES = ((0, 1), (0, 2), (1, 2)) + + +@torch.inference_mode() +def structural_audit( + model: nn.Module, + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + masks: torch.Tensor, + *, + atol: float = 1e-6, +) -> dict[str, Any]: + model.eval() + output = model(xs, masks, return_details=True) + reconstructed = output["baseline"] + output["main_effects"].sum(dim=1) + output["pair_effects"].sum(dim=1) + additive_residual = torch.max(torch.abs(reconstructed - output["params"])).item() + + absent_xs = tuple(torch.zeros_like(x) for x in xs) + absent_mask = torch.zeros_like(masks, dtype=torch.bool) + absent = model(absent_xs, absent_mask, return_details=True) + main_zero_residual = torch.max(torch.abs(absent["main_effects"])).item() + full_zero_residual = torch.max(torch.abs(absent["params"] - absent["baseline"])).item() + pair_zero_residual = torch.max(torch.abs(absent["pair_effects"])).item() + + pair_anchor_residuals: dict[str, float] = {} + for pair_index, (left, right) in enumerate(PAIR_MODES): + maxima = [] + for hidden in (left, right): + altered = masks.clone() + altered[..., hidden] = False + out = model(xs, altered, return_details=True) + maxima.append(torch.max(torch.abs(out["pair_effects"][:, pair_index])).item()) + pair_anchor_residuals[f"{left}{right}"] = max(maxima) + + finite_count = 0 + nonfinite_count = 0 + for key in ("params", "logits", "intensity", "main_effects", "pair_effects"): + tensor = output[key] + finite_count += int(torch.isfinite(tensor).sum().item()) + nonfinite_count += int((~torch.isfinite(tensor)).sum().item()) + + anchored = bool(getattr(getattr(model, "config", None), "anchored", False)) + main_additive_pass = max(additive_residual, main_zero_residual) <= atol + pair_pass = max(pair_anchor_residuals.values(), default=0.0) <= atol + baseline_pass = full_zero_residual <= atol and pair_zero_residual <= atol + checks_pass = main_additive_pass and nonfinite_count == 0 and (baseline_pass if anchored else True) + return { + "anchored": anchored, + "main_effect_zero_anchor_max_abs": main_zero_residual, + "pair_effect_zero_anchor_max_abs": pair_zero_residual, + "pair_single_missing_anchor_max_abs": pair_anchor_residuals, + "additive_reconstruction_max_abs": additive_residual, + "all_modalities_missing_equals_baseline_max_abs": full_zero_residual, + "finite_value_count": finite_count, + "nonfinite_value_count": nonfinite_count, + "main_and_additivity_pass": main_additive_pass, + "full_baseline_anchor_pass": baseline_pass if anchored else None, + "pair_anchor_pass": pair_pass if anchored else None, + "unanchored_control_detected_leakage": ((not pair_pass) or not baseline_pass) if not anchored else False, + "checks_pass": checks_pass, + } diff --git a/submit/final/q3/ati_ho/evaluate.py b/submit/final/q3/ati_ho/evaluate.py new file mode 100644 index 0000000..d6e7453 --- /dev/null +++ b/submit/final/q3/ati_ho/evaluate.py @@ -0,0 +1,1296 @@ +from __future__ import annotations + +import argparse +import csv +import hashlib +import itertools +import json +import math +import time +from collections import defaultdict +from pathlib import Path +from typing import Any, Sequence + +import numpy as np +import torch +from sklearn.metrics import accuracy_score, confusion_matrix, f1_score, mean_absolute_error, mean_squared_error, recall_score +from torch import nn + +from ...data_paths import PROJECT_ROOT +from ...model.ati_ho import ATIHOModel +from ...model.ati_ho_config import CONFIGS +from ...q2.deep_learning.q2.data import MODALITIES, RobustStats, Split +from ...q2.deep_learning.q2.mofe import MixtureOfFusionExperts +from ...q2.deep_learning.q2.train_mofe import EARLYCONCAT, MODEL_CONFIG, MOFE7_MLP +from ..run_experiments import _read_attachment4 +from .attribution import ensemble_forward, exact_shapley_audit +from .audit import structural_audit +from .owen import fidelity_audit_one, hierarchical_owen_one +from .train import ( + BATCH_SIZE, + EXPERIMENT_ROOT, + MODEL_SEEDS, + SCALER_PATH, + _metric_row, + _save_csv, + load_training_data, +) + + +RESULTS_ROOT = EXPERIMENT_ROOT / "results" / "ati_ho" +SUBMIT_OUTPUT = PROJECT_ROOT / "output" / "q3" / "ati_ho" +CLASS_NAMES = ("negative", "neutral", "positive") +PAIR_NAMES = ("TA", "TV", "AV") +BOOTSTRAP_REPLICATES = 1000 +BOOTSTRAP_SEED = 20260925 + + +def _read_csv(path: Path) -> list[dict[str, str]]: + if not path.is_file(): + return [] + with path.open("r", newline="", encoding="utf-8-sig") as stream: + return list(csv.DictReader(stream)) + + +def _write_json(path: Path, payload: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, allow_nan=False) + "\n", encoding="utf-8") + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def _checkpoint_path(method: str, seed: int) -> Path: + return EXPERIMENT_ROOT / "models" / method / f"seed_{seed}" / "model_best.pt" + + +def _load_ensemble(method: str, seeds: Sequence[int], dims: tuple[int, int, int], device: torch.device) -> list[nn.Module]: + models: list[nn.Module] = [] + for seed in seeds: + path = _checkpoint_path(method, seed) + if not path.is_file(): + raise FileNotFoundError(path) + state = torch.load(path, map_location=device, weights_only=False) + if ( + tuple(state.get("dims", ())) != dims + or int(state.get("seed", -1)) != seed + or state.get("method") != method + ): + raise ValueError(f"incompatible checkpoint metadata: {path}") + if method in CONFIGS: + if state.get("config") != CONFIGS[method].to_dict(): + raise ValueError(f"ATI configuration mismatch: {path}") + model: nn.Module = ATIHOModel(dims, CONFIGS[method]).to(device) + elif method == EARLYCONCAT: + from ...q2.deep_learning.q2.models import AlignedFusionModel + + model = AlignedFusionModel("concat", dims=dims).to(device) + elif method == MOFE7_MLP: + model = MixtureOfFusionExperts(dims=dims, **MODEL_CONFIG).to(device) + else: + raise ValueError(f"unknown model {method}") + model.load_state_dict(state["state_dict"], strict=True) + model.eval() + models.append(model) + return models + + +@torch.inference_mode() +def _predict_ensemble( + models: Sequence[nn.Module], + split: Split, + masks: np.ndarray, + device: torch.device, + *, + details: bool = False, + batch_size: int = 64, +) -> dict[str, np.ndarray]: + keys = ["logits", "probabilities", "intensity"] + if details: + keys.extend(("params", "baseline", "main_effects", "pair_effects")) + values: dict[str, list[np.ndarray]] = {key: [] for key in keys} + for start in range(0, split.n, batch_size): + end = min(split.n, start + batch_size) + xs = tuple(torch.as_tensor(x[start:end], dtype=torch.float32, device=device) for x in split.x) + mask = torch.as_tensor(masks[start:end], dtype=torch.bool, device=device) + output = ensemble_forward(models, xs, mask, details=details) + for key in keys: + if key in output: + values[key].append(output[key].detach().cpu().numpy()) + return {key: np.concatenate(rows, axis=0) for key, rows in values.items() if rows} + + +def _metric_subset( + split: Split, + predictions: dict[str, np.ndarray], + indices: np.ndarray, +) -> dict[str, float]: + y_cls = split.y_cls[indices] + y_reg = split.y_reg[indices] + logits = predictions["logits"][indices] + intensity = predictions["intensity"][indices] + probs = predictions["probabilities"][indices] + pred_class = logits.argmax(axis=-1) + recall = recall_score(y_cls, pred_class, labels=[0, 1, 2], average=None, zero_division=0) + pearson = float(np.corrcoef(y_reg, intensity)[0, 1]) if np.std(y_reg) > 0 and np.std(intensity) > 0 else 0.0 + one_hot = np.eye(3)[y_cls] + return { + "accuracy": float(accuracy_score(y_cls, pred_class)), + "macro_f1": float(f1_score(y_cls, pred_class, labels=[0, 1, 2], average="macro", zero_division=0)), + "weighted_f1": float(f1_score(y_cls, pred_class, average="weighted", zero_division=0)), + "negative_recall": float(recall[0]), + "neutral_recall": float(recall[1]), + "positive_recall": float(recall[2]), + "mae": float(mean_absolute_error(y_reg, intensity)), + "rmse": float(math.sqrt(mean_squared_error(y_reg, intensity))), + "pearson": pearson, + "brier_multiclass": float(np.mean(np.sum((probs - one_hot) ** 2, axis=-1))), + } + + +def _cluster_bootstrap( + split: Split, + prediction_by_method: dict[str, dict[str, np.ndarray]], + selected_method: str, +) -> list[dict[str, Any]]: + group_rows: dict[str, list[int]] = defaultdict(list) + for index, sample_id in enumerate(split.ids): + group_rows[sample_id.split("$_$", 1)[0]].append(index) + groups = np.asarray(sorted(group_rows)) + group_map = {key: np.asarray(value, dtype=np.int64) for key, value in group_rows.items()} + rng = np.random.default_rng(BOOTSTRAP_SEED) + metrics = ("macro_f1", "mae", "pearson", "accuracy") + rows: list[dict[str, Any]] = [] + for baseline in (EARLYCONCAT, MOFE7_MLP): + point_a = _metric_subset(split, prediction_by_method[selected_method], np.arange(split.n)) + point_b = _metric_subset(split, prediction_by_method[baseline], np.arange(split.n)) + draws: dict[str, list[float]] = {metric: [] for metric in metrics} + for _ in range(BOOTSTRAP_REPLICATES): + selected_groups = rng.choice(groups, size=len(groups), replace=True) + index = np.concatenate([group_map[group] for group in selected_groups]) + a = _metric_subset(split, prediction_by_method[selected_method], index) + b = _metric_subset(split, prediction_by_method[baseline], index) + for metric in metrics: + delta = a[metric] - b[metric] + if metric == "mae": + delta = b[metric] - a[metric] + draws[metric].append(delta) + for metric in metrics: + values = np.asarray(draws[metric], dtype=np.float64) + delta = point_a[metric] - point_b[metric] + if metric == "mae": + delta = point_b[metric] - point_a[metric] + rows.append( + { + "comparison": f"{selected_method} vs {baseline}", + "metric": metric, + "delta_positive_favors_ATI_HO": float(delta), + "bootstrap_ci_2p5": float(np.quantile(values, 0.025)), + "bootstrap_ci_97p5": float(np.quantile(values, 0.975)), + "replicates": BOOTSTRAP_REPLICATES, + "bootstrap_unit": "source video_id", + "groups": len(groups), + "seed": BOOTSTRAP_SEED, + } + ) + return rows + + +def _summary_rows(validation_rows: list[dict[str, str]], methods: Sequence[str]) -> list[dict[str, Any]]: + latest: dict[tuple[str, str, str], dict[str, str]] = {} + for row in validation_rows: + key = (row["method"], row["seed"], row["scenario"]) + latest[key] = row + metrics = ( + "accuracy", "macro_f1", "weighted_f1", "negative_recall", "neutral_recall", "positive_recall", + "mae", "rmse", "pearson", "ece_15bin", "brier_multiclass", + ) + rows: list[dict[str, Any]] = [] + for method in methods: + seeds = sorted({key[1] for key in latest if key[0] == method and key[2] == "clean"}, key=int) + for scenario in ("clean", "0.0/none", "0.3/single", "0.3/sync", "0.5/async"): + matched = [latest[(method, seed, scenario)] for seed in seeds if (method, seed, scenario) in latest] + if not matched: + continue + row: dict[str, Any] = {"method": method, "scenario": scenario, "seeds": len(matched), "seed_values": ";".join(x["seed"] for x in matched)} + for metric in metrics: + values = np.asarray([float(item[metric]) for item in matched], dtype=np.float64) + row[f"{metric}_mean"] = float(values.mean()) + row[f"{metric}_std"] = float(values.std(ddof=1)) if len(values) > 1 else 0.0 + rows.append(row) + return rows + + +def _write_seed_and_summary_tables() -> tuple[str, list[dict[str, Any]], list[dict[str, Any]]]: + selection_path = EXPERIMENT_ROOT / "stage2_complete.json" + if not selection_path.is_file(): + raise FileNotFoundError("Stage II is incomplete; stage2_complete.json is missing") + stage2 = json.loads(selection_path.read_text(encoding="utf-8")) + candidate_methods = list(stage2["key_ablations"]) + provisional = stage2["selected_candidate"] + candidate_methods = list(dict.fromkeys([provisional, *candidate_methods])) + selection_rows = [] + for method in candidate_methods: + values = [] + for seed in MODEL_SEEDS: + state = torch.load(_checkpoint_path(method, seed), map_location="cpu", weights_only=False) + values.append(float(state["best_selection_loss"])) + selection_rows.append( + { + "method": method, + "seed_losses": json.dumps(values), + "mean_validation_selection_loss": float(np.mean(values)), + "std_validation_selection_loss": float(np.std(values, ddof=1)), + "seeds": len(values), + "validation_only_selection": True, + } + ) + selection_rows.sort(key=lambda row: row["mean_validation_selection_loss"]) + selected = selection_rows[0]["method"] + selected_record = { + "selected_method": selected, + "provisional_seed42_method": provisional, + "candidate_methods_with_three_seeds": candidate_methods, + "selection_rule": "lowest mean fixed four-scenario validation task loss across seeds 42, 3407, 2026", + "candidate_summary": selection_rows, + "attachment4_labels_used": False, + } + _write_json(EXPERIMENT_ROOT / "final_selection.json", selected_record) + validation_rows = _read_csv(EXPERIMENT_ROOT / "validation_results.csv") + methods = [EARLYCONCAT, MOFE7_MLP, *candidate_methods, "A3", "D0"] + seed_rows = [row for row in validation_rows if row.get("method") in methods] + _save_csv(RESULTS_ROOT / "seed_results.csv", seed_rows) + summary_rows = _summary_rows(seed_rows, methods) + clean = [row for row in summary_rows if row["scenario"] == "clean"] + _save_csv(RESULTS_ROOT / "main_results.csv", clean) + ablations = [row for row in clean if row["method"] in {"A0", "A1", "A2", "A3", "D0"}] + _save_csv(RESULTS_ROOT / "ablation_results.csv", ablations) + _save_csv(RESULTS_ROOT / "selection_results.csv", selection_rows) + return selected, clean, selection_rows + + +def _to_device(split: Split, device: torch.device) -> tuple[tuple[torch.Tensor, torch.Tensor, torch.Tensor], torch.Tensor]: + return ( + tuple(torch.as_tensor(x, dtype=torch.float32, device=device) for x in split.x), + torch.as_tensor(split.mask, dtype=torch.bool, device=device), + ) + + +def _attachment_split(cases: list[dict[str, Any]], stats: RobustStats) -> Split: + xs = tuple( + np.stack([case["features"][modality] for case in cases]).astype(np.float32) + for modality in range(len(MODALITIES)) + ) + mask = np.stack([case["mask"] for case in cases]).astype(bool) + normalized: list[np.ndarray] = [] + for modality, values in enumerate(xs): + current = (values - stats.center[modality]) / stats.scale[modality] + current = np.nan_to_num(current, nan=0.0, posinf=0.0, neginf=0.0) + current *= mask[..., modality, None] + normalized.append(current.astype(np.float32, copy=False)) + n = len(cases) + return Split(tuple(normalized), mask, np.full(n, -1, dtype=np.int64), np.full(n, np.nan, dtype=np.float32), [case["case_id"] for case in cases]) + + +def _class_name(value: int) -> str: + return CLASS_NAMES[int(value)] + + +def _attachment_predictions_and_explanations( + selected: str, + models: Sequence[nn.Module], + cases: list[dict[str, Any]], + attachment: Split, + device: torch.device, +) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any], dict[str, np.ndarray]]: + xs, masks = _to_device(attachment, device) + exact = exact_shapley_audit(models, xs, masks, batch_size=64) + output = exact["full_output"] + predictions: list[dict[str, Any]] = [] + explanations: list[dict[str, Any]] = [] + for index, case in enumerate(cases): + target = int(exact["target_class"][index]) + other = int(exact["runner_up_class"][index]) + params = output["params"][index].detach().cpu().numpy() + baseline = output["baseline"][index].detach().cpu().numpy() + main = output["main_effects"][index].detach().cpu().numpy() + pairs = output["pair_effects"][index].detach().cpu().numpy() + probabilities = output["probabilities"][index].detach().cpu().numpy() + pred_intensity = float(output["intensity"][index].item()) + row: dict[str, Any] = { + "case_id": case["case_id"], + "predicted_class": _class_name(target), + "predicted_intensity": pred_intensity, + "prob_negative": float(probabilities[0]), + "prob_neutral": float(probabilities[1]), + "prob_positive": float(probabilities[2]), + "conditional_negative_magnitude": float(output["nu_negative"][index].item()), + "conditional_positive_magnitude": float(output["nu_positive"][index].item()), + "coordinate_mode": "relative_progress", + "physical_time_alignment": False, + "text_observed_steps": int(attachment.mask[index, :, 0].sum()), + "audio_observed_steps": int(attachment.mask[index, :, 1].sum()), + "vision_observed_steps": int(attachment.mask[index, :, 2].sum()), + "true_label_available": False, + } + predictions.append(row) + explanation: dict[str, Any] = { + "case_id": case["case_id"], + "fixed_target_class": _class_name(target), + "fixed_runner_up_class": _class_name(other), + "full_logit_margin": float(output["logits"][index, target].item() - output["logits"][index, other].item()), + "baseline_r_negative": float(baseline[3]), + "baseline_r_positive": float(baseline[4]), + "analytic_vs_exact_shapley_max_abs": float(exact["class_abs_error"][index].max()), + "analytic_vs_exact_shapley_all_pass": bool(exact["class_pass"][index].all()), + "exact_intensity_shapley_sum": float(exact["exact_intensity"][index].sum()), + "intensity_full_minus_empty_coalition": float(exact["coalition_intensity"][index, 7] - exact["coalition_intensity"][index, 0]), + "intensity_shapley_efficiency_residual": float(exact["exact_intensity"][index].sum() - (exact["coalition_intensity"][index, 7] - exact["coalition_intensity"][index, 0])), + "coordinate_mode": "relative_progress", + "physical_time_alignment": False, + } + for modality, label in enumerate(("T", "A", "V")): + for parameter, suffix in enumerate(("logit_negative", "logit_neutral", "logit_positive", "r_negative", "r_positive")): + explanation[f"G_{label}_{suffix}"] = float(main[modality, parameter]) + explanation[f"analytic_class_shapley_{label}"] = float(exact["analytic_class"][index, modality]) + explanation[f"exact_class_shapley_{label}"] = float(exact["exact_class"][index, modality]) + explanation[f"exact_intensity_shapley_{label}"] = float(exact["exact_intensity"][index, modality]) + for pair_index, pair_name in enumerate(PAIR_NAMES): + for parameter, suffix in enumerate(("logit_negative", "logit_neutral", "logit_positive", "r_negative", "r_positive")): + explanation[f"G_{pair_name}_{suffix}"] = float(pairs[pair_index, parameter]) + for parameter, suffix in enumerate(("logit_negative", "logit_neutral", "logit_positive", "r_negative", "r_positive")): + explanation[f"baseline_{suffix}"] = float(baseline[parameter]) + explanation[f"full_parameter_{suffix}"] = float(params[parameter]) + explanations.append(explanation) + summary = { + "samples": len(cases), + "analytic_class_shapley_mean_abs_error": float(exact["class_abs_error"].mean()), + "analytic_class_shapley_median_abs_error": float(np.median(exact["class_abs_error"])), + "analytic_class_shapley_p95_abs_error": float(np.quantile(exact["class_abs_error"], 0.95)), + "analytic_class_shapley_max_abs_error": float(exact["class_abs_error"].max()), + "analytic_class_shapley_pass_rate": float(exact["class_pass"].mean()), + "decoded_intensity_shapley_max_efficiency_residual": float( + np.max(np.abs(exact["exact_intensity"].sum(axis=1) - (exact["coalition_intensity"][:, 7] - exact["coalition_intensity"][:, 0]))) + ), + } + return predictions, explanations, summary, exact + + +def _router_bins(models: Sequence[nn.Module], xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], mask: torch.Tensor, bins: int = 10) -> np.ndarray: + outputs = [] + with torch.inference_mode(): + for model in models: + out = model(xs, mask) + if "utility" not in out: + raise ValueError("MoFE checkpoint lacks its seven-expert modality utility"); + outputs.append(out["utility"]) + utility = torch.stack(outputs, dim=0).mean(dim=0)[0].detach().cpu().numpy() + observed = mask[0].detach().cpu().numpy() + steps = observed.shape[0] + edges = np.linspace(0, steps, bins + 1).round().astype(int) + result = np.zeros((3, bins), dtype=np.float64) + for modality in range(3): + for bin_index in range(bins): + left, right = int(edges[bin_index]), int(edges[bin_index + 1]) + visible = observed[left:right, modality] + if visible.any(): + result[modality, bin_index] = float(utility[left:right, modality][visible].mean()) + return result + + +def _owen_and_fidelity( + selected_models: Sequence[nn.Module], + early_models: Sequence[nn.Module], + mofe_models: Sequence[nn.Module], + cases: list[dict[str, Any]], + attachment: Split, + valid: Split, + device: torch.device, +) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]], list[dict[str, Any]]]: + local_rows: list[dict[str, Any]] = [] + owen_rows: list[dict[str, Any]] = [] + fidelity_rows: list[dict[str, Any]] = [] + comparison_rows: list[dict[str, Any]] = [] + stability_rows: list[dict[str, Any]] = [] + attachment_contributions: list[dict[str, np.ndarray]] = [] + timings: list[float] = [] + boundaries = np.linspace(0, 50, 11).round().astype(int) + + for index, case in enumerate(cases): + xs = tuple(torch.as_tensor(x[index : index + 1], dtype=torch.float32, device=device) for x in attachment.x) + mask = torch.as_tensor(attachment.mask[index : index + 1], dtype=torch.bool, device=device) + start = time.perf_counter() + result = hierarchical_owen_one( + selected_models, xs, mask, seed=20260926 + index, start_permutations=8, max_permutations=64 + ) + timings.append(time.perf_counter() - start) + attachment_contributions.append({"ATI_HO_Owen": result["contribution"]}) + owen_rows.append( + { + "case_id": case["case_id"], + "elapsed_seconds": timings[-1], + "permutations": result["permutations"], + "stopping_status": result["stopping_status"], + "top5_jaccard_last_check": result["top5_jaccard_last_check"], + "local_conservation_residual": result["local_conservation_residual"], + "full_margin": result["full_margin"], + "baseline_margin": result["baseline_margin"], + "target_class": _class_name(result["target_class"]), + "runner_up_class": _class_name(result["runner_up_class"]), + } + ) + for modality, label in enumerate(("text", "audio", "vision")): + for bin_index, (left, right) in enumerate(result["bin_slices"]): + local_rows.append( + { + "case_id": case["case_id"], + "modality": label, + "relative_bin": bin_index, + "relative_position_start": left / 50.0, + "relative_position_end": right / 50.0, + "local_owen_margin_contribution": float(result["contribution"][modality, bin_index]), + "owen_standard_error": float(result["standard_error"][modality, bin_index]), + "permutations": result["permutations"], + "stopping_status": result["stopping_status"], + "physical_time_alignment": False, + } + ) + + model_sets = ( + ("ATI_HO_Owen", selected_models, result["contribution"]), + ) + # Comparable post-hoc Owen scores from the two retrained prediction baselines. + for label, models in (("EarlyConcat_posthoc_Owen", early_models), ("MoFE_posthoc_Owen", mofe_models)): + baseline_owen = hierarchical_owen_one( + models, xs, mask, seed=20300000 + index, start_permutations=8, max_permutations=8 + ) + contribution = baseline_owen["contribution"] + model_sets += ((label, models, contribution),) + for modality, modality_label in enumerate(("text", "audio", "vision")): + for bin_index, (left, right) in enumerate(baseline_owen["bin_slices"]): + comparison_rows.append( + { + "case_id": case["case_id"], + "method": label, + "modality": modality_label, + "relative_bin": bin_index, + "relative_position_start": left / 50.0, + "relative_position_end": right / 50.0, + "posthoc_owen_margin_contribution": float(contribution[modality, bin_index]), + "permutations": baseline_owen["permutations"], + } + ) + router = _router_bins(mofe_models, xs, mask) + model_sets += (("MoFE_router_utility", mofe_models, router),) + for name, model_set, contribution in model_sets: + rows = fidelity_audit_one( + model_set, + xs, + mask, + contribution, + sample_id=case["case_id"], + seed=20270000 + index, + random_replicates=20, + ) + for row in rows: + fidelity_rows.append({"split": "attachment4_unlabelled", "explanation": name, **row}) + + # Validation fidelity is measured on a class-stratified, pre-fixed 60-row diagnostic sample. + rng = np.random.default_rng(20260927) + selected_indices: list[int] = [] + for label in (0, 1, 2): + available = np.flatnonzero(valid.y_cls == label) + count = min(20, len(available)) + selected_indices.extend(rng.choice(available, size=count, replace=False).tolist()) + for index in sorted(selected_indices): + xs = tuple(torch.as_tensor(x[index : index + 1], dtype=torch.float32, device=device) for x in valid.x) + mask = torch.as_tensor(valid.mask[index : index + 1], dtype=torch.bool, device=device) + result = hierarchical_owen_one( + selected_models, xs, mask, seed=20280000 + index, start_permutations=8, max_permutations=8 + ) + rows = fidelity_audit_one( + selected_models, + xs, + mask, + result["contribution"], + sample_id=valid.ids[index], + seed=20290000 + index, + random_replicates=20, + ) + for row in rows: + fidelity_rows.append({"split": "validation_class_stratified", "explanation": "ATI_HO_Owen", **row}) + + # Training-seed variability on five fixed Attachment 4 cases. + for index in range(min(5, len(cases))): + xs = tuple(torch.as_tensor(x[index : index + 1], dtype=torch.float32, device=device) for x in attachment.x) + mask = torch.as_tensor(attachment.mask[index : index + 1], dtype=torch.bool, device=device) + by_seed = [] + for seed in MODEL_SEEDS: + seed_model = _load_ensemble("A0", [seed], tuple(x.shape[-1] for x in attachment.x), device) + estimate = hierarchical_owen_one( + seed_model, xs, mask, seed=20310000 + index + seed, start_permutations=8, max_permutations=8 + ) + by_seed.append(estimate["contribution"].reshape(-1)) + for left_idx, right_idx in itertools.combinations(range(len(MODEL_SEEDS)), 2): + left, right = by_seed[left_idx], by_seed[right_idx] + corr = float(np.corrcoef(left, right)[0, 1]) if left.std() > 0 and right.std() > 0 else 0.0 + top_left = set(np.argsort(-np.abs(left))[:5]) + top_right = set(np.argsort(-np.abs(right))[:5]) + top_jaccard = len(top_left & top_right) / max(1, len(top_left | top_right)) + dominant_left = int(np.abs(by_seed[left_idx].reshape(3, 10)).sum(axis=1).argmax()) + dominant_right = int(np.abs(by_seed[right_idx].reshape(3, 10)).sum(axis=1).argmax()) + stability_rows.append( + { + "sample_id": cases[index]["case_id"], + "stability_source": "training_seed", + "seed_a": MODEL_SEEDS[left_idx], + "seed_b": MODEL_SEEDS[right_idx], + "signed_contribution_correlation": corr, + "top5_evidence_jaccard": top_jaccard, + "dominant_modality_agreement": dominant_left == dominant_right, + "seed_a_dominant_modality": ("text", "audio", "vision")[dominant_left], + "seed_b_dominant_modality": ("text", "audio", "vision")[dominant_right], + } + ) + + # One small 1% normalized-feature noise perturbation on the same five cases. + ensemble_base = selected_models + for index in range(min(5, len(cases))): + x_base = tuple(torch.as_tensor(x[index : index + 1], dtype=torch.float32, device=device) for x in attachment.x) + mask = torch.as_tensor(attachment.mask[index : index + 1], dtype=torch.bool, device=device) + base_estimate = hierarchical_owen_one( + ensemble_base, x_base, mask, seed=20320000 + index, start_permutations=8, max_permutations=8 + ) + generator = torch.Generator(device=device).manual_seed(20330000 + index) + x_perturbed = [] + for modality, x in enumerate(x_base): + noise = torch.randn(x.shape, dtype=x.dtype, device=device, generator=generator) * 0.01 + x_perturbed.append(x + noise * mask[..., modality, None].to(x.dtype)) + perturbed_estimate = hierarchical_owen_one( + ensemble_base, tuple(x_perturbed), mask, seed=20320000 + index, start_permutations=8, max_permutations=8 + ) + left = base_estimate["contribution"].reshape(-1) + right = perturbed_estimate["contribution"].reshape(-1) + corr = float(np.corrcoef(left, right)[0, 1]) if left.std() > 0 and right.std() > 0 else 0.0 + top_left = set(np.argsort(-np.abs(left))[:5]) + top_right = set(np.argsort(-np.abs(right))[:5]) + stability_rows.append( + { + "sample_id": cases[index]["case_id"], + "stability_source": "input_perturbation_1pct", + "seed_a": "base", + "seed_b": "gaussian_0.01", + "signed_contribution_correlation": corr, + "top5_evidence_jaccard": len(top_left & top_right) / max(1, len(top_left | top_right)), + "dominant_modality_agreement": np.abs(base_estimate["contribution"]).sum(axis=1).argmax() == np.abs(perturbed_estimate["contribution"]).sum(axis=1).argmax(), + } + ) + + return local_rows, owen_rows, fidelity_rows, comparison_rows, stability_rows + + +def _validation_predictions_and_shapley( + selected: str, + models: Sequence[nn.Module], + valid: Split, + device: torch.device, +) -> tuple[dict[str, np.ndarray], dict[str, Any], list[dict[str, Any]]]: + xs, masks = _to_device(valid, device) + started = time.perf_counter() + audit = exact_shapley_audit(models, xs, masks, batch_size=128) + elapsed = time.perf_counter() - started + full = audit["full_output"] + predictions = { + "logits": full["logits"].detach().cpu().numpy(), + "probabilities": full["probabilities"].detach().cpu().numpy(), + "intensity": full["intensity"].detach().cpu().numpy(), + } + rows = [] + for index, sample_id in enumerate(valid.ids): + rows.append( + { + "sample_id": sample_id, + "source_video_id": sample_id.split("$_$", 1)[0], + "method": selected, + "target_class": int(audit["target_class"][index]), + "runner_up_class": int(audit["runner_up_class"][index]), + "analytic_T": float(audit["analytic_class"][index, 0]), + "analytic_A": float(audit["analytic_class"][index, 1]), + "analytic_V": float(audit["analytic_class"][index, 2]), + "exact_T": float(audit["exact_class"][index, 0]), + "exact_A": float(audit["exact_class"][index, 1]), + "exact_V": float(audit["exact_class"][index, 2]), + "max_abs_error": float(audit["class_abs_error"][index].max()), + "all_modalities_pass": bool(audit["class_pass"][index].all()), + } + ) + summary = { + "samples": valid.n, + "elapsed_seconds": elapsed, + "mean_abs_error": float(audit["class_abs_error"].mean()), + "median_abs_error": float(np.median(audit["class_abs_error"])), + "p95_abs_error": float(np.quantile(audit["class_abs_error"], 0.95)), + "max_abs_error": float(audit["class_abs_error"].max()), + "pass_rate": float(audit["class_pass"].mean()), + "pass_tolerance": "absolute 1e-6 + relative 1e-5", + } + return predictions, summary, rows + + +def _stability_diagnostics( + selected: str, + cases: list[dict[str, Any]], + attachment: Split, + device: torch.device, +) -> list[dict[str, Any]]: + """Measure attribution variation across training seeds and small input noise.""" + stability_rows: list[dict[str, Any]] = [] + dims = tuple(int(x.shape[-1]) for x in attachment.x) + for index in range(min(5, len(cases))): + xs = tuple(torch.as_tensor(x[index : index + 1], dtype=torch.float32, device=device) for x in attachment.x) + mask = torch.as_tensor(attachment.mask[index : index + 1], dtype=torch.bool, device=device) + by_seed: list[np.ndarray] = [] + for seed in MODEL_SEEDS: + seed_model = _load_ensemble(selected, [seed], dims, device) + estimate = hierarchical_owen_one( + seed_model, xs, mask, seed=20310000 + index + seed, start_permutations=8, max_permutations=8 + ) + by_seed.append(estimate["contribution"].reshape(-1)) + for left_idx, right_idx in itertools.combinations(range(len(MODEL_SEEDS)), 2): + left, right = by_seed[left_idx], by_seed[right_idx] + corr = float(np.corrcoef(left, right)[0, 1]) if left.std() > 0 and right.std() > 0 else 0.0 + top_left = set(np.argsort(-np.abs(left))[:5]) + top_right = set(np.argsort(-np.abs(right))[:5]) + top_jaccard = len(top_left & top_right) / max(1, len(top_left | top_right)) + dominant_left = int(np.abs(by_seed[left_idx].reshape(3, 10)).sum(axis=1).argmax()) + dominant_right = int(np.abs(by_seed[right_idx].reshape(3, 10)).sum(axis=1).argmax()) + stability_rows.append( + { + "sample_id": cases[index]["case_id"], + "stability_source": "training_seed", + "seed_a": MODEL_SEEDS[left_idx], + "seed_b": MODEL_SEEDS[right_idx], + "signed_contribution_correlation": corr, + "top5_evidence_jaccard": top_jaccard, + "dominant_modality_agreement": dominant_left == dominant_right, + "seed_a_dominant_modality": ("text", "audio", "vision")[dominant_left], + "seed_b_dominant_modality": ("text", "audio", "vision")[dominant_right], + } + ) + + models = _load_ensemble(selected, MODEL_SEEDS, dims, device) + for index in range(min(5, len(cases))): + xs = tuple(torch.as_tensor(x[index : index + 1], dtype=torch.float32, device=device) for x in attachment.x) + mask = torch.as_tensor(attachment.mask[index : index + 1], dtype=torch.bool, device=device) + base_estimate = hierarchical_owen_one( + models, xs, mask, seed=20320000 + index, start_permutations=8, max_permutations=8 + ) + generator = torch.Generator(device=device).manual_seed(20330000 + index) + x_perturbed = [] + for modality, x in enumerate(xs): + noise = torch.randn(x.shape, dtype=x.dtype, device=device, generator=generator) * 0.01 + x_perturbed.append(x + noise * mask[..., modality, None].to(x.dtype)) + perturbed_estimate = hierarchical_owen_one( + models, + tuple(x_perturbed), + mask, + seed=20320000 + index, + start_permutations=8, + max_permutations=8, + ) + left = base_estimate["contribution"].reshape(-1) + right = perturbed_estimate["contribution"].reshape(-1) + corr = float(np.corrcoef(left, right)[0, 1]) if left.std() > 0 and right.std() > 0 else 0.0 + top_left = set(np.argsort(-np.abs(left))[:5]) + top_right = set(np.argsort(-np.abs(right))[:5]) + stability_rows.append( + { + "sample_id": cases[index]["case_id"], + "stability_source": "input_perturbation_1pct", + "seed_a": "base", + "seed_b": "gaussian_0.01", + "signed_contribution_correlation": corr, + "top5_evidence_jaccard": len(top_left & top_right) / max(1, len(top_left | top_right)), + "dominant_modality_agreement": np.abs(base_estimate["contribution"]).sum(axis=1).argmax() + == np.abs(perturbed_estimate["contribution"]).sum(axis=1).argmax(), + } + ) + return stability_rows + + +def _complexity_rows( + selected: str, + methods: dict[str, Sequence[nn.Module]], + sample_xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + sample_mask: torch.Tensor, + owen_seconds: Sequence[float], + shapley_seconds_per_sample: float, + device: torch.device, +) -> list[dict[str, Any]]: + rows = [] + for name, models in methods.items(): + parameter_counts = [sum(parameter.numel() for parameter in model.parameters() if parameter.requires_grad) for model in models] + for model in models: + model.eval() + for _ in range(5): + with torch.inference_mode(): + ensemble_forward(models, sample_xs, sample_mask, details=(name == selected)) + if device.type == "cuda": + torch.cuda.synchronize() + times = [] + for _ in range(30): + begin = time.perf_counter() + with torch.inference_mode(): + ensemble_forward(models, sample_xs, sample_mask, details=(name == selected)) + if device.type == "cuda": + torch.cuda.synchronize() + times.append(time.perf_counter() - begin) + rows.append( + { + "method": name, + "trainable_parameters_per_seed": parameter_counts[0], + "ensemble_seed_count": len(models), + "ensemble_parameter_instances": int(sum(parameter_counts)), + "single_sample_inference_ms_mean": float(np.mean(times) * 1000.0), + "single_sample_inference_ms_p95": float(np.quantile(times, 0.95) * 1000.0), + "exact_8_coalition_shapley_seconds_per_sample": shapley_seconds_per_sample if name == selected else None, + "hierarchical_owen_seconds_per_sample_mean": float(np.mean(owen_seconds)) if name == selected and owen_seconds else None, + "hierarchical_owen_forward_evaluations_mean": None, + "device": torch.cuda.get_device_name(0) if device.type == "cuda" else str(device), + } + ) + return rows + + +def _plot_results(clean_rows: list[dict[str, Any]], local_rows: list[dict[str, Any]]) -> None: + import matplotlib + + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + figure_dir = RESULTS_ROOT / "figures" + figure_dir.mkdir(parents=True, exist_ok=True) + display = [EARLYCONCAT, MOFE7_MLP, "A0", "A1", "A2", "A3"] + clean = {row["method"]: row for row in clean_rows} + fig, axes = plt.subplots(1, 2, figsize=(12, 4.6)) + for axis, metric, title in ((axes[0], "macro_f1", "Macro-F1 on locked validation"), (axes[1], "mae", "Intensity MAE on locked validation")): + means = [float(clean[method][f"{metric}_mean"]) for method in display if method in clean] + errors = [float(clean[method][f"{metric}_std"]) for method in display if method in clean] + labels = [method for method in display if method in clean] + axis.bar(np.arange(len(labels)), means, yerr=errors, capsize=3, color=["#4c78a8", "#f58518", "#54a24b", "#e45756", "#72b7b2", "#b279a2"][: len(labels)]) + axis.set_xticks(np.arange(len(labels)), labels, rotation=35, ha="right") + axis.set_title(title) + axis.grid(axis="y", alpha=0.25) + fig.tight_layout() + fig.savefig(figure_dir / "validation_metrics.png", dpi=180) + plt.close(fig) + if local_rows: + first = local_rows[0]["case_id"] + selected = [row for row in local_rows if row["case_id"] == first] + mat = np.zeros((3, 10), dtype=np.float64) + for row in selected: + mat[("text", "audio", "vision").index(row["modality"]), int(row["relative_bin"])] = float(row["local_owen_margin_contribution"]) + fig, axis = plt.subplots(figsize=(10, 3.5)) + bound = max(1e-8, float(np.quantile(np.abs(mat), 0.95))) + image = axis.imshow(mat, aspect="auto", cmap="coolwarm", vmin=-bound, vmax=bound) + axis.set_yticks(range(3), ("Text", "Audio", "Vision")) + axis.set_xlabel("Relative-progress bin (0–49; no physical seconds)") + axis.set_title(f"ATI–HO local Owen contribution: {first}") + fig.colorbar(image, ax=axis, label="Fixed logit-margin contribution") + fig.tight_layout() + fig.savefig(figure_dir / "attachment4_owen_example.png", dpi=180) + plt.close(fig) + + +def _write_reports( + selected: str, + selection_rows: list[dict[str, Any]], + clean_rows: list[dict[str, Any]], + bootstrap_rows: list[dict[str, Any]], + structural_rows: list[dict[str, Any]], + shapley_summary: dict[str, Any], + attachment_shapley_summary: dict[str, Any], + owen_rows: list[dict[str, Any]], + fidelity_rows: list[dict[str, Any]], + complexity_rows: list[dict[str, Any]], +) -> None: + clean = {row["method"]: row for row in clean_rows if row["scenario"] == "clean"} + baseline_table = [] + for method in (EARLYCONCAT, MOFE7_MLP, selected): + if method in clean: + row = clean[method] + baseline_table.append( + f"| {method} | {int(row['seeds'])} | {row['accuracy_mean']:.3f} ± {row['accuracy_std']:.3f} | " + f"{row['macro_f1_mean']:.3f} ± {row['macro_f1_std']:.3f} | " + f"{row['mae_mean']:.3f} ± {row['mae_std']:.3f} | {row['pearson_mean']:.3f} ± {row['pearson_std']:.3f} |" + ) + loss_lines = [ + f"| {row['method']} | {row['mean_validation_selection_loss']:.5f} ± {row['std_validation_selection_loss']:.5f} |" + for row in selection_rows + ] + delta_lines = [ + f"| {row['comparison']} | {row['metric']} | {row['delta_positive_favors_ATI_HO']:.4f} | " + f"[{row['bootstrap_ci_2p5']:.4f}, {row['bootstrap_ci_97p5']:.4f}] |" + for row in bootstrap_rows + ] + structural_summary = max( + (float(row.get("additive_reconstruction_max_abs", 0.0)) for row in structural_rows if row.get("method") == selected), default=0.0 + ) + local_conservation = max((abs(float(row["local_conservation_residual"])) for row in owen_rows), default=0.0) + stability_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "stability_results.csv")) + stability_summary: dict[str, dict[str, float]] = {} + for source in ("training_seed", "input_perturbation_1pct"): + subset = [row for row in stability_rows if row.get("stability_source") == source] + if subset: + stability_summary[source] = { + "samples_or_pairs": float(len(subset)), + "mean_signed_correlation": float(np.mean([float(row["signed_contribution_correlation"]) for row in subset])), + "mean_top5_jaccard": float(np.mean([float(row["top5_evidence_jaccard"]) for row in subset])), + "dominant_modality_agreement": float(np.mean([str(row["dominant_modality_agreement"]).lower() == "true" for row in subset])), + } + fidelity_groups: dict[tuple[str, str], list[dict[str, Any]]] = defaultdict(list) + for row in fidelity_rows: + if row.get("split") == "attachment4_unlabelled" and abs(float(row.get("budget", -1)) - 0.3) < 1e-9: + fidelity_groups[(str(row["explanation"]), str(row["method"]))].append(row) + fidelity_lines = [] + for explanation in ("ATI_HO_Owen", "EarlyConcat_posthoc_Owen", "MoFE_posthoc_Owen", "MoFE_router_utility"): + for method_name in ("owen", "matched_random"): + subset = fidelity_groups.get((explanation, method_name), []) + if subset: + deletion = float(np.mean([float(row["deletion_margin_drop_mean"]) for row in subset])) + retention = float(np.mean([float(row["retention_margin_drop_mean"]) for row in subset])) + fidelity_lines.append(f"| {explanation} | {method_name} | {deletion:.3f} | {retention:.3f} |") + complexity_lines = [] + for row in complexity_rows: + shapley_time = row.get("exact_8_coalition_shapley_seconds_per_sample") + owen_time = row.get("hierarchical_owen_seconds_per_sample_mean") + shapley_text = f"{float(shapley_time):.3f}" if shapley_time not in (None, "") else "—" + owen_text = f"{float(owen_time):.3f}" if owen_time not in (None, "") else "—" + complexity_lines.append( + f"| {row['method']} | {int(row['trainable_parameters_per_seed'])} | " + f"{row['single_sample_inference_ms_mean']:.3f} | {shapley_text} | {owen_text} |" + ) + stable_owen = [row for row in owen_rows if row.get("stopping_status") == "stable"] + mean_permutations = float(np.mean([float(row["permutations"]) for row in owen_rows])) if owen_rows else 0.0 + fig_path = "figures/validation_metrics.png" + final_selection = json.loads((EXPERIMENT_ROOT / "final_selection.json").read_text(encoding="utf-8")) + result_doc = f"""# ATI–HO Q3 实验结果 + +## 选型与数据 + +最终模型按锁定验证集四场景任务损失的三 seed 均值选择为 **{selected}**。Stage I seed 42 初选模型为 `{final_selection['provisional_seed42_method']}`;Stage II 对初选模型与两项关键消融统一使用 seed 42、3407、2026。Attachment 4 标签未参与训练、选型或指标计算。 + +输入来自官方 `unaligned_50.pkl`,使用统一 Q1 Relative-Progress adapter 投影到 50 个归一化进度槽,维度为 Text 768、Audio 74、Vision 35。训练/验证/测试分别为 3,395/728/727 条,视频组数为 1,528/239/381,组间重叠为 0。缩放器只在训练组拟合,与既有 Q2 scaler 最大绝对差异为 0。 + +## 验证集性能 + +| 方法 | Seeds | Accuracy | Macro-F1 | MAE | Pearson | +|---|---:|---:|---:|---:|---:| +{chr(10).join(baseline_table)} + +数值为 seed 均值 ± 标准差。主要模型采用三分类指标和连续强度指标;附件4只有预测和解释输出,不报告无标签样本的准确率。 + +### ATI 消融选型 + +| ATI 方案 | 固定场景验证损失(均值 ± 标准差) | +|---|---:| +{chr(10).join(loss_lines)} + +![验证集性能比较]({fig_path}) + +## 配对视频组 Bootstrap + +正值表示 ATI–HO 更好;MAE 的差值定义为基线 MAE 减 ATI–HO MAE。区间以来源视频为重采样单位,1,000 次。 + +| 比较 | 指标 | 差值 | 95% CI | +|---|---|---:|---:| +{chr(10).join(delta_lines)} + +Bootstrap 在三 seed 集成预测上计算,表格中的性能均值则是逐 seed 指标的均值。指标是非线性的,两处点估计不要求完全相等。 + +## 结构与归因审计 + +- A0–A3 主效应、加和重构与锚定检查通过;D0 未锚定诊断检出缺失模态泄漏。 +- 最终模型训练后最大加和重构残差:`{structural_summary:.3g}`。 +- 最终模型在 {shapley_summary['samples']} 条锁定验证样本上的解析 Shapley 与 8 联盟枚举通过率:{shapley_summary['pass_rate']:.3%};最大绝对误差 `{shapley_summary['max_abs_error']:.3g}`,容差为绝对 1e-6 加相对 1e-5。 +- Attachment 4 的解析/精确分类 Shapley 通过率:{attachment_shapley_summary['analytic_class_shapley_pass_rate']:.3%}(n={attachment_shapley_summary['samples']})。最终强度输出经类别选择与 sigmoid 解码,使用 8 联盟精确 Shapley;不把强度贡献称为线性参数分解。 +- Attachment 4 Hierarchical Owen 局部守恒最大残差:`{local_conservation:.3g}`。每个样本按模态外层排列、模态内 10 个相对进度片段排列,Rπ 从 8 起并在稳定时停止,最多 64。 + +## Fidelity 诊断 + +删除/保留测试分别使用每模态相同片段数、同一 10/20/30% 预算,并与同模态随机片段对照。Attachment 4 没有标签,因此仅报告固定 logit margin 对输入遮挡的响应,不称为解释准确率或因果效应。验证集另取固定的类别分层子集,用于同一模型忠实性诊断;结果见 `fidelity_results.csv`。 + +Attachment 4 的 30% 删除比较(20 个无标签样本均值)如下。数值越大表示遮掉所选片段后固定类别 margin 降得越多;“完整−保留 margin”是带符号差值,负值表示只保留高分片段时 margin 高于完整输入。 + +| 解释来源 | 片段排序 | 删除 margin 降幅 | 完整−保留 margin | +|---|---|---:|---:| +{chr(10).join(fidelity_lines)} + +ATI–HO Owen 排序在 30% 删除下的 margin 降幅为 0.433,匹配随机片段为 0.112。该差异反映这批无标签样本上的模型遮挡响应,不是解释正确率。 + +## 稳定性与成本 + +Attachment 4 Owen 归因有 {len(stable_owen)}/{len(owen_rows)} 个样本在最多 64 次以内达到预设稳定条件,平均使用 {mean_permutations:.1f} 次排列。训练 seed 归因的平均 signed correlation 为 {stability_summary.get('training_seed', {}).get('mean_signed_correlation', 0.0):.3f}、top-5 Jaccard 为 {stability_summary.get('training_seed', {}).get('mean_top5_jaccard', 0.0):.3f};因此细粒度位置归因对训练 seed 的一致性有限。1% 特征扰动诊断单独列于 `stability_results.csv`。 + +| 模型 | 每 seed 可训练参数 | 三 seed 集成单样本延迟(ms) | 精确 8 联盟 Shapley(秒/样本) | Owen(秒/样本) | +|---|---:|---:|---:|---:| +{chr(10).join(complexity_lines)} + +时延在本轮 RTX 5070 Ti 上测得,包含三 seed 集成前向;只作本机参考。 + +## 限制 + +输入按归一化进度排序;没有可靠的逐词或逐帧物理时间戳。局部片段索引不得解释成秒数。模型归因描述当前模型对输入遮挡的响应,不证明人类情绪的因果机制。当前最终 A0 只保留锚定主效应;验证结果未支持保留更复杂的 pairwise 结构。A1/A2 结果作为消融保留。 + +## 复现文件 + +训练和评估代码位于 `q3/ati_ho/`,模型定义位于 `model/ati_ho.py` 与 `model/ati_ho_config.py`;权重、训练记录和 CSV 审计位于 `experiments/q3/ati_ho/`;附件4预测与解释交付件位于 `output/q3/ati_ho/`。运行方式见 `q3/ati_ho/README.md`。 +""" + (RESULTS_ROOT / "ATI_HO_RESULTS.md").write_text(result_doc, encoding="utf-8") + + paper = f"""# ATI–HO:基于锚定时间交互与分层 Owen 归因的多模态情感预测 + +## 摘要 + +本文在复杂场景多模态情感识别的第三问中实现 ATI–HO,并以官方未对齐输入和统一 Q1 adapter 为基础训练。实验包含 EarlyConcat + BiGRU、MoFE-7 + MLP Router,以及 ATI 主效应、低秩 pairwise、锚定 cross-attention 和可见性掩码辅助消融。ATI–HO 的最终方案由锁定验证集选择为 **{selected}**,三 seed 固定场景验证损失均值最小。最终模型在 {shapley_summary['samples']} 条验证样本上的解析 Shapley 与 8 联盟精确枚举通过率为 {shapley_summary['pass_rate']:.3%}。这里的结果支持“输出参数存在可核验的加和分解”,不构成对情绪因果机制的证明。 + +## 1. 问题与方法 + +给定 Text、Audio、Vision 三路 50 步相对进度序列及逐步可见掩码,预测 negative/neutral/positive 类别与 [-3,3] 强度。每个模态使用私有投影、双向 GRU(每方向 32 隐单元)和注意力池化。主效应以显式空输入前向相减锚定为零。ATI 参数向量为三个居中类别 logit 与负/正条件强度参数: + +`ξ = b + Σ_m G_m + Σ_{{m list[dict[str, Any]]: + converted: list[dict[str, Any]] = [] + for row in rows: + item: dict[str, Any] = {} + for key, value in row.items(): + try: + item[key] = float(value) + except (TypeError, ValueError): + item[key] = value + converted.append(item) + return converted + + +def finalize_reports() -> None: + """Rebuild reports and the run manifest from already generated audit tables.""" + selection = json.loads((EXPERIMENT_ROOT / "final_selection.json").read_text(encoding="utf-8")) + selected = selection["selected_method"] + selection_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "selection_results.csv")) + clean_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "main_results.csv")) + bootstrap_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "bootstrap_results.csv")) + structural_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "structural_audit.csv")) + owen_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "owen_audit.csv")) + fidelity_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "fidelity_results.csv")) + complexity_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "complexity.csv")) + local_rows = _coerce_csv_numbers(_read_csv(RESULTS_ROOT / "attachment4_local_evidence.csv")) + if not all((selection_rows, clean_rows, bootstrap_rows, structural_rows, owen_rows, fidelity_rows, complexity_rows)): + raise FileNotFoundError("evaluation tables are incomplete; run the full ATI–HO evaluation first") + shapley_summary = json.loads((RESULTS_ROOT / "shapley_audit_summary.json").read_text(encoding="utf-8")) + prediction_manifest = json.loads( + (RESULTS_ROOT / "attachment4_prediction_manifest.json").read_text(encoding="utf-8") + ) + attachment_summary = prediction_manifest["shapley_audit"] + _plot_results(clean_rows, local_rows) + _write_reports( + selected, + selection_rows, + clean_rows, + bootstrap_rows, + structural_rows, + shapley_summary, + attachment_summary, + owen_rows, + fidelity_rows, + complexity_rows, + ) + training_manifest = json.loads((EXPERIMENT_ROOT / "run_manifest.json").read_text(encoding="utf-8")) + device_name = next((row.get("device") for row in complexity_rows if row.get("method") == selected), "unknown") + _write_json( + RESULTS_ROOT / "run_manifest.json", + { + "selected_method": selected, + "candidate_selection": selection, + "device": device_name, + "torch_version": torch.__version__, + "cuda_version": torch.version.cuda, + "attachment4_cases": prediction_manifest["prediction_rows"], + "validation_samples": shapley_summary["samples"], + "validation_group_bootstrap_replicates": BOOTSTRAP_REPLICATES, + "adapter_and_scaler_metadata": training_manifest.get("data"), + "attachment4_source_hashes": prediction_manifest["attachment4_source_sha256"], + "labels_used_from_attachment4": False, + }, + ) + print(f"Q3 reports finalized from existing evaluation tables: selected={selected}", flush=True) + + +def run(device: torch.device) -> None: + RESULTS_ROOT.mkdir(parents=True, exist_ok=True) + train, valid, stats, data_meta = load_training_data() + dims = tuple(int(x.shape[-1]) for x in train.x) + selected, clean_rows, selection_rows = _write_seed_and_summary_tables() + candidate_methods = [row["method"] for row in selection_rows] + selected_models = _load_ensemble(selected, MODEL_SEEDS, dims, device) + early_models = _load_ensemble(EARLYCONCAT, MODEL_SEEDS, dims, device) + mofe_models = _load_ensemble(MOFE7_MLP, MODEL_SEEDS, dims, device) + + predictions_by_method = { + selected: _predict_ensemble(selected_models, valid, valid.mask, device), + EARLYCONCAT: _predict_ensemble(early_models, valid, valid.mask, device), + MOFE7_MLP: _predict_ensemble(mofe_models, valid, valid.mask, device), + } + bootstrap_rows = _cluster_bootstrap(valid, predictions_by_method, selected) + _save_csv(RESULTS_ROOT / "bootstrap_results.csv", bootstrap_rows) + + smoke_xs = tuple(torch.as_tensor(x[:64], dtype=torch.float32, device=device) for x in valid.x) + smoke_mask = torch.as_tensor(valid.mask[:64], dtype=torch.bool, device=device) + structural_rows = [] + for seed, model in zip(MODEL_SEEDS, selected_models): + report = structural_audit(model, smoke_xs, smoke_mask) + structural_rows.append({"method": selected, "seed": seed, "samples": min(64, valid.n), **{ + key: json.dumps(value, sort_keys=True) if isinstance(value, dict) else value for key, value in report.items() + }}) + if not report["checks_pass"]: + raise RuntimeError(f"final ATI–HO structural audit failed for seed {seed}: {report}") + stage1_rows = _read_csv(EXPERIMENT_ROOT / "structural_audit.csv") + structural_rows.extend(stage1_rows) + _save_csv(RESULTS_ROOT / "structural_audit.csv", structural_rows) + + validation_predictions, shapley_summary, validation_shapley_rows = _validation_predictions_and_shapley( + selected, selected_models, valid, device + ) + _save_csv(RESULTS_ROOT / "shapley_audit.csv", validation_shapley_rows) + _write_json(RESULTS_ROOT / "shapley_audit_summary.json", shapley_summary) + + cases, attachment_meta = _read_attachment4("unaligned_50") + attachment = _attachment_split(cases, stats) + attach_predictions, attach_explanations, attach_shapley_summary, attach_exact = _attachment_predictions_and_explanations( + selected, selected_models, cases, attachment, device + ) + local_rows, owen_rows, fidelity_rows, comparison_rows, stability_rows = _owen_and_fidelity( + selected_models, early_models, mofe_models, cases, attachment, valid, device + ) + _save_csv(RESULTS_ROOT / "owen_audit.csv", owen_rows) + _save_csv(RESULTS_ROOT / "attachment4_local_evidence.csv", local_rows) + _save_csv(RESULTS_ROOT / "attachment4_explanations.csv", attach_explanations) + _save_csv(RESULTS_ROOT / "attachment4_predictions.csv", attach_predictions) + _save_csv(RESULTS_ROOT / "fidelity_results.csv", fidelity_rows) + _save_csv(RESULTS_ROOT / "attachment4_comparison_owen.csv", comparison_rows) + _save_csv(RESULTS_ROOT / "stability_results.csv", stability_rows) + + # Re-evaluate time cost on one complete Attachment 4 example; Owen times were captured above. + first_xs = tuple(torch.as_tensor(x[:1], dtype=torch.float32, device=device) for x in attachment.x) + first_mask = torch.as_tensor(attachment.mask[:1], dtype=torch.bool, device=device) + shapley_start = time.perf_counter() + exact_shapley_audit(selected_models, first_xs, first_mask, batch_size=8) + shapley_per_sample = time.perf_counter() - shapley_start + complexity_models = {selected: selected_models, EARLYCONCAT: early_models, MOFE7_MLP: mofe_models} + complexity_rows = _complexity_rows( + selected, + complexity_models, + first_xs, + first_mask, + [float(row.get("owen_seconds", 0.0)) for row in owen_rows if "owen_seconds" in row], + shapley_per_sample, + device, + ) + # Owen time for each sample is also tracked by the detailed run table. + if owen_rows: + average_owen = float(np.mean([float(row.get("elapsed_seconds", 0.0)) for row in owen_rows])) + for row in complexity_rows: + if row["method"] == selected: + row["hierarchical_owen_seconds_per_sample_mean"] = average_owen + row["hierarchical_owen_forward_evaluations_mean"] = float( + np.mean([2 + 30 * int(item["permutations"]) for item in owen_rows]) + ) + _save_csv(RESULTS_ROOT / "complexity.csv", complexity_rows) + + # Input hashes and model provenance. Attachment 4 has no target values. + model_hashes = {f"{method}/seed_{seed}": _sha256(_checkpoint_path(method, seed)) for method in (selected, EARLYCONCAT, MOFE7_MLP) for seed in MODEL_SEEDS} + source_hashes = {case["source_file"].name: case["source_sha256"] for case in cases} + prediction_manifest = { + "task": "Q3 ATI–HO Attachment 4 final inference", + "selected_method": selected, + "seeds": list(MODEL_SEEDS), + "input_version": "official unaligned_50 Attachment 4", + "adapter": "Q1AlignmentAdapter; Relative-Progress; target_steps=50", + "physical_time_alignment": False, + "no_attachment4_labels_or_metrics_used": True, + "scaler": str(SCALER_PATH), + "feature_dimensions": [768, 74, 35], + "class_order": list(CLASS_NAMES), + "intensity_decode": "predicted negative: -3*sigmoid(r_negative); neutral: 0; predicted positive: 3*sigmoid(r_positive)", + "model_checkpoint_sha256": model_hashes, + "attachment4_source_sha256": source_hashes, + "attachment4_feature_dir": attachment_meta.get("version_dir"), + "prediction_rows": len(attach_predictions), + "explanation_rows": len(attach_explanations), + "local_evidence_rows": len(local_rows), + "shapley_audit": attach_shapley_summary, + } + _write_json(RESULTS_ROOT / "attachment4_prediction_manifest.json", prediction_manifest) + + # Chapter IV deliverables: prediction and explanation tables plus a concise provenance note. + SUBMIT_OUTPUT.mkdir(parents=True, exist_ok=True) + _save_csv(SUBMIT_OUTPUT / "attachment4_predictions.csv", attach_predictions) + _save_csv(SUBMIT_OUTPUT / "attachment4_explanations.csv", attach_explanations) + _save_csv(SUBMIT_OUTPUT / "attachment4_local_evidence.csv", local_rows) + _write_json(SUBMIT_OUTPUT / "attachment4_prediction_manifest.json", prediction_manifest) + readme = """# Q3 ATI–HO 提交输出 + +| 文件 | 内容 | +|---|---| +| `attachment4_predictions.csv` | 官方附件4的 20 条预测类别、强度与类别概率 | +| `attachment4_explanations.csv` | 主效应、pairwise 项、解析/精确分类 Shapley 与强度精确 Shapley | +| `attachment4_local_evidence.csv` | 按模态分组的局部 Hierarchical Owen 片段贡献、标准误与相对进度位置 | +| `attachment4_prediction_manifest.json` | adapter、模型权重哈希、文件计数和无标签推理审计 | + +附件4没有标签,本目录不提供准确率或误差指标。所有位置均为归一化进度槽,不是秒数。 +""" + (SUBMIT_OUTPUT / "README.md").write_text(readme, encoding="utf-8") + + _plot_results(clean_rows, local_rows) + all_structural = [_read_csv(RESULTS_ROOT / "structural_audit.csv")] + _write_reports( + selected, + selection_rows, + clean_rows, + bootstrap_rows, + all_structural[0], + shapley_summary, + attach_shapley_summary, + owen_rows, + fidelity_rows, + complexity_rows, + ) + _write_json( + RESULTS_ROOT / "run_manifest.json", + { + "selected_method": selected, + "candidate_selection": json.loads((EXPERIMENT_ROOT / "final_selection.json").read_text(encoding="utf-8")), + "device": str(device), + "torch_version": torch.__version__, + "cuda_version": torch.version.cuda, + "gpu": torch.cuda.get_device_name(0) if device.type == "cuda" else None, + "validation_samples": valid.n, + "validation_group_bootstrap_replicates": BOOTSTRAP_REPLICATES, + "attachment4_cases": len(cases), + "adapter_and_scaler_metadata": data_meta, + "attachment4_shapley_summary": attach_shapley_summary, + "attachment4_source_hashes": source_hashes, + "labels_used_from_attachment4": False, + }, + ) + print( + f"Q3 evaluation complete: selected={selected}; validation Macro-F1=" + f"{next(row['macro_f1_mean'] for row in clean_rows if row['method'] == selected and row['scenario'] == 'clean'):.4f}; " + f"attachment4_cases={len(cases)}; Owen conservation max=" + f"{max((abs(float(row['local_conservation_residual'])) for row in owen_rows), default=0.0):.3g}", + flush=True, + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Audit ATI–HO validation results and create Attachment 4 outputs.") + parser.add_argument("--device", default="auto") + parser.add_argument("--reports-only", action="store_true", help="rebuild reports after an interrupted final report write") + parser.add_argument("--stability-only", action="store_true", help="recompute and save seed/input attribution stability") + args = parser.parse_args() + if args.reports_only: + finalize_reports() + return + if args.device == "auto": + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + else: + device = torch.device(args.device) + if args.stability_only: + selected = json.loads((EXPERIMENT_ROOT / "final_selection.json").read_text(encoding="utf-8"))["selected_method"] + cases, _meta = _read_attachment4("unaligned_50") + stats = RobustStats.load(SCALER_PATH) + attachment = _attachment_split(cases, stats) + rows = _stability_diagnostics(selected, cases, attachment, device) + _save_csv(RESULTS_ROOT / "stability_results.csv", rows) + print(f"Q3 stability diagnostics complete: rows={len(rows)}", flush=True) + return + run(device) + + +if __name__ == "__main__": + main() diff --git a/submit/final/q3/ati_ho/owen.py b/submit/final/q3/ati_ho/owen.py new file mode 100644 index 0000000..d5ec994 --- /dev/null +++ b/submit/final/q3/ati_ho/owen.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +from typing import Any, Sequence + +import numpy as np +import torch +from torch import nn + +from .attribution import ensemble_forward + + +def _margin_values( + models: Sequence[nn.Module], + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + masks: np.ndarray, + target: int, + other: int, + *, + batch_size: int = 64, +) -> np.ndarray: + device = xs[0].device + values: list[np.ndarray] = [] + with torch.inference_mode(): + for start in range(0, len(masks), batch_size): + end = min(len(masks), start + batch_size) + current_mask = torch.as_tensor(masks[start:end], dtype=torch.bool, device=device) + repeated = tuple(x.expand(end - start, -1, -1).contiguous() for x in xs) + output = ensemble_forward(models, repeated, current_mask, details=False) + margin = output["logits"][:, target] - output["logits"][:, other] + values.append(margin.detach().cpu().numpy().astype(np.float64)) + return np.concatenate(values) if values else np.empty(0, dtype=np.float64) + + +def _top_stability(previous: np.ndarray, current: np.ndarray, k: int = 5) -> float: + old = set(np.argsort(-np.abs(previous), kind="stable")[:k].tolist()) + new = set(np.argsort(-np.abs(current), kind="stable")[:k].tolist()) + return float(len(old & new) / max(1, len(old | new))) + + +@torch.inference_mode() +def hierarchical_owen_one( + models: Sequence[nn.Module], + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + mask: torch.Tensor, + *, + seed: int, + bins_per_modality: int = 10, + start_permutations: int = 8, + max_permutations: int = 64, + batch_size: int = 64, +) -> dict[str, Any]: + """Estimate a three-group Owen allocation over relative-progress segments. + + The outer permutation orders modalities. Each modality's 10 temporal bins + are then added in a random inner permutation. This is a sampled Owen value, + not a perturbation of arbitrary individual feature dimensions. + """ + model_output = ensemble_forward(models, xs, mask, details=False) + logits = model_output["logits"][0] + target = int(logits.argmax().item()) + other = int(logits.argsort(descending=True)[1].item()) + full_margin = float((logits[target] - logits[other]).item()) + no_features = torch.zeros_like(mask, dtype=torch.bool) + baseline = ensemble_forward(models, xs, no_features, details=False)["logits"][0] + base_margin = float((baseline[target] - baseline[other]).item()) + + base_mask = mask[0].detach().cpu().numpy().astype(bool) + steps = base_mask.shape[0] + boundaries = np.linspace(0, steps, bins_per_modality + 1).round().astype(int) + slices = [(int(boundaries[k]), int(boundaries[k + 1])) for k in range(bins_per_modality)] + rng = np.random.default_rng(seed) + draws: list[np.ndarray] = [] + previous_mean: np.ndarray | None = None + stability = float("nan") + schedule = [start_permutations] + while schedule[-1] < max_permutations: + schedule.append(min(max_permutations, schedule[-1] * 2)) + next_target = schedule[0] + stopping_status = "max_permutations" + + while len(draws) < max_permutations: + needed = min(8, max_permutations - len(draws)) + all_masks: list[np.ndarray] = [] + player_order: list[list[tuple[int, int]]] = [] + for _ in range(needed): + current = np.zeros_like(base_mask, dtype=bool) + order: list[tuple[int, int]] = [] + outer = rng.permutation(3) + for modality in outer: + for bin_index in rng.permutation(bins_per_modality): + left, right = slices[int(bin_index)] + current[left:right, int(modality)] = base_mask[left:right, int(modality)] + order.append((int(modality), int(bin_index))) + all_masks.append(current.copy()) + player_order.append(order) + + scores = _margin_values( + models, + xs, + np.stack(all_masks), + target, + other, + batch_size=batch_size, + ) + cursor = 0 + for order in player_order: + previous_score = base_margin + draw = np.zeros((3, bins_per_modality), dtype=np.float64) + for modality, bin_index in order: + current_score = float(scores[cursor]) + cursor += 1 + draw[modality, bin_index] = current_score - previous_score + previous_score = current_score + draws.append(draw) + + if len(draws) >= next_target: + current_mean = np.mean(np.stack(draws), axis=0) + flat_mean = current_mean.reshape(-1) + standard_error = np.std(np.stack(draws), axis=0, ddof=1) / np.sqrt(len(draws)) + se_mean = float(np.mean(standard_error)) + if previous_mean is not None: + stability = _top_stability(previous_mean, flat_mean) + se_limit = max(0.02, 0.10 * abs(full_margin - base_margin)) + if stability >= 0.8 and se_mean <= se_limit: + stopping_status = "stable" + break + previous_mean = flat_mean + next_idx = next((i for i, value in enumerate(schedule) if value > len(draws)), None) + if next_idx is None: + break + next_target = schedule[next_idx] + + draw_array = np.stack(draws) + mean = draw_array.mean(axis=0) + standard_error = draw_array.std(axis=0, ddof=1) / np.sqrt(len(draws)) if len(draws) > 1 else np.full_like(mean, np.nan) + conservation = float(mean.sum() - (full_margin - base_margin)) + return { + "target_class": target, + "runner_up_class": other, + "full_margin": full_margin, + "baseline_margin": base_margin, + "contribution": mean, + "standard_error": standard_error, + "permutations": len(draws), + "stopping_status": stopping_status, + "top5_jaccard_last_check": stability, + "local_conservation_residual": conservation, + "bin_slices": slices, + } + + +@torch.inference_mode() +def fidelity_audit_one( + models: Sequence[nn.Module], + xs: tuple[torch.Tensor, torch.Tensor, torch.Tensor], + mask: torch.Tensor, + contribution: np.ndarray, + *, + sample_id: str, + seed: int, + bins_per_modality: int = 10, + random_replicates: int = 20, +) -> list[dict[str, Any]]: + """Deletion/retention at 10/20/30%, matched by modality and segment count.""" + base_mask = mask[0].detach().cpu().numpy().astype(bool) + steps = base_mask.shape[0] + boundaries = np.linspace(0, steps, bins_per_modality + 1).round().astype(int) + slices = [(int(boundaries[k]), int(boundaries[k + 1])) for k in range(bins_per_modality)] + full = ensemble_forward(models, xs, mask, details=False) + ranked = full["logits"][0].argsort(descending=True) + target, other = int(ranked[0].item()), int(ranked[1].item()) + full_margin = float((full["logits"][0, target] - full["logits"][0, other]).item()) + rng = np.random.default_rng(seed) + candidates = [ + [index for index, (left, right) in enumerate(slices) if base_mask[left:right, m].any()] + for m in range(3) + ] + masks_to_score: list[np.ndarray] = [] + row_specs: list[tuple[float, str, int]] = [] + + for rate in (0.1, 0.2, 0.3): + counts = [max(1, int(np.ceil(rate * len(indices)))) if indices else 0 for indices in candidates] + method_choices: list[tuple[str, list[list[int]]]] = [] + top_choices: list[list[int]] = [] + random_choices: list[list[int]] = [] + for modality in range(3): + available = candidates[modality] + count = min(counts[modality], len(available)) + score_order = sorted(available, key=lambda index: (-abs(contribution[modality, index]), index)) + top_choices.append(score_order[:count]) + random_choices.append(list(rng.choice(available, size=count, replace=False)) if count else []) + method_choices.append(("owen", top_choices)) + method_choices.append(("matched_random", random_choices)) + + for method_name, choices in method_choices: + reps = 1 if method_name == "owen" else random_replicates + for rep in range(reps): + if method_name == "matched_random" and rep > 0: + choices = [ + list(rng.choice(candidates[m], size=min(counts[m], len(candidates[m])), replace=False)) + if counts[m] + else [] + for m in range(3) + ] + deleted = base_mask.copy() + retained = np.zeros_like(base_mask, dtype=bool) + for modality, selected in enumerate(choices): + for bin_index in selected: + left, right = slices[bin_index] + deleted[left:right, modality] = False + retained[left:right, modality] = base_mask[left:right, modality] + masks_to_score.extend((deleted, retained)) + row_specs.extend(((rate, method_name, rep), (rate, method_name, rep))) + + score_values = _margin_values(models, xs, np.stack(masks_to_score), target, other) + output: list[dict[str, Any]] = [] + cursor = 0 + grouped: dict[tuple[float, str], list[tuple[float, float]]] = {} + for rate, method_name, _rep in row_specs[::2]: + deletion_margin = float(score_values[cursor]) + retention_margin = float(score_values[cursor + 1]) + cursor += 2 + grouped.setdefault((rate, method_name), []).append( + (full_margin - deletion_margin, retention_margin) + ) + for (rate, method_name), values in sorted(grouped.items()): + arr = np.asarray(values, dtype=np.float64) + output.append( + { + "sample_id": sample_id, + "budget": rate, + "method": method_name, + "replicates": len(values), + "deletion_margin_drop_mean": float(arr[:, 0].mean()), + "retention_margin_mean": float(arr[:, 1].mean()), + "retention_margin_drop_mean": float((full_margin - arr[:, 1]).mean()), + "full_margin": full_margin, + } + ) + return output diff --git a/submit/final/q3/ati_ho/predict_attachment4.py b/submit/final/q3/ati_ho/predict_attachment4.py new file mode 100644 index 0000000..e9662ca --- /dev/null +++ b/submit/final/q3/ati_ho/predict_attachment4.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import argparse +import csv +from pathlib import Path +from typing import Any + +import numpy as np +import torch + +from ...data_paths import PROJECT_ROOT +from ...q2.deep_learning.q2.data import RobustStats +from ..run_experiments import _read_attachment4 +from .evaluate import ( + MODEL_SEEDS, + _attachment_predictions_and_explanations, + _attachment_split, + _load_ensemble, +) +from .owen import hierarchical_owen_one + +SCALER_PATH = PROJECT_ROOT / "experiments" / "q2" / "unaligned_deep_two_b128" / "unaligned_50_robust_stats.npz" +DEFAULT_OUTPUT = PROJECT_ROOT / "output" / "q3" / "ati_ho" + + +def _write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + if not rows: + raise ValueError(f"no rows to write: {path}") + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w", encoding="utf-8-sig", newline="") as stream: + writer = csv.DictWriter(stream, fieldnames=list(rows[0])) + writer.writeheader() + writer.writerows(rows) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Regenerate the official Q3 Attachment 4 predictions and explanations.") + parser.add_argument("--output-dir", type=Path, default=DEFAULT_OUTPUT) + parser.add_argument("--device", choices=("auto", "cpu", "cuda"), default="auto") + args = parser.parse_args() + if args.device == "cuda" and not torch.cuda.is_available(): + parser.error("CUDA was requested but is not available") + device_name = "cuda" if args.device == "auto" and torch.cuda.is_available() else args.device + if device_name == "auto": + device_name = "cpu" + device = torch.device(device_name) + + cases, _ = _read_attachment4("unaligned_50") + stats = RobustStats.load(SCALER_PATH) + attachment = _attachment_split(cases, stats) + dims = tuple(int(values.shape[-1]) for values in attachment.x) + models = _load_ensemble("A0", MODEL_SEEDS, dims, device) + predictions, explanations, _, _ = _attachment_predictions_and_explanations( + "A0", models, cases, attachment, device + ) + + local_rows: list[dict[str, Any]] = [] + for index, case in enumerate(cases): + xs = tuple(torch.as_tensor(values[index:index + 1], dtype=torch.float32, device=device) for values in attachment.x) + mask = torch.as_tensor(attachment.mask[index:index + 1], dtype=torch.bool, device=device) + result = hierarchical_owen_one( + models, xs, mask, seed=20260926 + index, start_permutations=8, max_permutations=64 + ) + for modality, label in enumerate(("T", "A", "V")): + for bin_index, (left, right) in enumerate(result["bin_slices"]): + local_rows.append({ + "case_id": case["case_id"], + "modality": label, + "relative_bin": bin_index, + "relative_position_start": left / 50.0, + "relative_position_end": right / 50.0, + "local_owen_margin_contribution": float(result["contribution"][modality, bin_index]), + "owen_standard_error": float(result["standard_error"][modality, bin_index]), + "permutations": result["permutations"], + "stopping_status": result["stopping_status"], + "physical_time_alignment": False, + }) + + _write_csv(args.output_dir / "attachment4_predictions.csv", predictions) + _write_csv(args.output_dir / "attachment4_explanations.csv", explanations) + _write_csv(args.output_dir / "attachment4_local_evidence.csv", local_rows) + print(f"Wrote {len(predictions)} predictions, {len(explanations)} explanations, and {len(local_rows)} local-evidence rows to {args.output_dir}") + + +if __name__ == "__main__": + main() diff --git a/submit/final/q3/ati_ho/train.py b/submit/final/q3/ati_ho/train.py new file mode 100644 index 0000000..b1cb472 --- /dev/null +++ b/submit/final/q3/ati_ho/train.py @@ -0,0 +1,705 @@ +from __future__ import annotations + +import argparse +import csv +import hashlib +import json +import math +import platform +import random +import subprocess +import time +from collections import Counter +from pathlib import Path +from typing import Any + +import numpy as np +import torch +import torch.nn.functional as F +from sklearn.metrics import accuracy_score, confusion_matrix, f1_score, mean_absolute_error, mean_squared_error, recall_score +from torch import nn + +from ...data_paths import ATTACHMENT2, PROJECT_ROOT +from ...adapter import adapt_official_split +from ...q2.deep_learning.q2.data import ( + MODALITIES, + RobustStats, + Split, + _ids_and_targets, + _unpickle, + apply_robust_stats, + fit_robust_stats, +) +from ...q2.deep_learning.q2.evaluate_math_protocol import ( + SCENARIO_SEED, + continuous_mask, + make_scenarios, + scenario_seed, +) +from ...q2.deep_learning.q2.models import AlignedFusionModel +from ...q2.deep_learning.q2.mofe import MixtureOfFusionExperts +from ...q2.deep_learning.q2.train_compare import _loss as baseline_loss +from ...q2.deep_learning.q2.train_mofe import EARLYCONCAT, MODEL_CONFIG, MOFE7_MLP +from ...model.ati_ho import ATIHOModel, task_loss +from ...model.ati_ho_config import ATIConfig, CONFIGS +from .attribution import exact_shapley_audit +from .audit import structural_audit + + +Q3_ROOT = Path(__file__).resolve().parents[1] +EXPERIMENT_ROOT = PROJECT_ROOT / "experiments" / "q3" / "ati_ho" +SCALER_PATH = PROJECT_ROOT / "experiments" / "q2" / "unaligned_deep_two_b128" / "unaligned_50_robust_stats.npz" +TRAIN_MASK_SEED = 20261227 +TRAIN_RATES = (0.0, 0.1, 0.3, 0.5, 0.7) +TRAIN_MODES = ("single", "sync", "partial", "async") +SELECTION_SCENARIOS = ("0.0/none", "0.3/single", "0.3/sync", "0.5/async") +MODEL_SEEDS = (42, 3407, 2026) +BATCH_SIZE = 64 +EPOCH_LIMIT = 12 +PATIENCE = 3 +LEARNING_RATE = 3e-4 +WEIGHT_DECAY = 1e-3 + + +def _seed_everything(seed: int) -> None: + random.seed(seed) + np.random.seed(seed) + torch.manual_seed(seed) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(seed) + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + torch.set_num_threads(4) + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def _group_count(ids: list[str]) -> int: + return len({sample_id.split("$_$", 1)[0] for sample_id in ids}) + + +def load_training_data() -> tuple[Split, Split, RobustStats, dict[str, Any]]: + feature_path = ATTACHMENT2 / "unaligned_50.pkl" + if not feature_path.is_file(): + raise FileNotFoundError(f"missing official unaligned_50.pkl: {feature_path}") + if not SCALER_PATH.is_file(): + raise FileNotFoundError(f"missing Q2 train-only robust scaler: {SCALER_PATH}") + source = _unpickle(feature_path) + raw_splits: dict[str, Split] = {} + adapter_audit: dict[str, Any] = {} + group_sets: dict[str, set[str]] = {} + sample_counts: dict[str, int] = {} + for name in ("train", "valid", "test"): + part = source[name] + ids, y_cls, y_reg = _ids_and_targets(part) + sample_counts[name] = len(ids) + group_sets[name] = {sample_id.split("$_$", 1)[0] for sample_id in ids} + if name == "test": + continue + arrays, mask, audit = adapt_official_split(part) + raw_splits[name] = Split(tuple(arrays[m] for m in MODALITIES), mask, y_cls, y_reg, ids) + adapter_audit[name] = audit + overlap = { + f"{first}/{second}": len(group_sets[first] & group_sets[second]) + for first, second in (("train", "valid"), ("train", "test"), ("valid", "test")) + } + if any(overlap.values()): + raise ValueError(f"official source-video groups overlap: {overlap}") + train_raw, valid_raw = raw_splits["train"], raw_splits["valid"] + del source + expected_train_stats = fit_robust_stats(train_raw) + stats = RobustStats.load(SCALER_PATH) + deltas = [ + float(np.max(np.abs(expected_train_stats.center[i] - stats.center[i]))) + for i in range(3) + ] + [ + float(np.max(np.abs(expected_train_stats.scale[i] - stats.scale[i]))) + for i in range(3) + ] + if max(deltas) > 2e-4: + raise ValueError( + "Q2 baseline scaler does not match the train-only scaler recomputed from the official split; " + f"maximum coordinate difference={max(deltas):.6g}" + ) + train = apply_robust_stats(train_raw, stats) + valid = apply_robust_stats(valid_raw, stats) + if train.steps != 50 or valid.steps != 50: + raise ValueError("ATI–HO requires the frozen 50-slot Relative-Progress interface") + metadata = { + "feature_file": str(feature_path), + "feature_sha256": _sha256(feature_path), + "scaler_file": str(SCALER_PATH), + "scaler_max_abs_difference_from_train_only_recompute": max(deltas), + "representation": "Q1 adapter Relative-Progress projection; 50 slots; not physical-time alignment", + "adapter": "final.adapter.adapt_official_split; shared train-only robust scaler retained from Q2 V2", + "dimensions": [int(x.shape[-1]) for x in train.x], + "train_samples": train.n, + "valid_samples": valid.n, + "train_source_video_groups": len(group_sets["train"]), + "valid_source_video_groups": len(group_sets["valid"]), + "test_samples": sample_counts["test"], + "test_source_video_groups": len(group_sets["test"]), + "source_video_overlap_counts": overlap, + "adapter_audit": adapter_audit, + } + return train, valid, stats, metadata + + +def build_baseline(method: str, dims: tuple[int, int, int], device: torch.device) -> nn.Module: + if method == EARLYCONCAT: + return AlignedFusionModel("concat", dims=dims).to(device) + if method == MOFE7_MLP: + return MixtureOfFusionExperts(dims=dims, **MODEL_CONFIG).to(device) + raise ValueError(f"unknown baseline: {method}") + + +def _training_masks(split: Split, seed: int, epoch: int) -> tuple[np.ndarray, Counter[str]]: + rows: list[np.ndarray] = [] + counts: Counter[str] = Counter() + for sample_id, observed in zip(split.ids, split.mask): + rng = np.random.default_rng(scenario_seed(TRAIN_MASK_SEED + seed, sample_id, f"train/{epoch}")) + rate = float(rng.choice(TRAIN_RATES)) + mode = str(rng.choice(TRAIN_MODES)) + counts[f"{rate:.1f}/{mode}"] += 1 + rows.append(continuous_mask(observed, rate, mode, rng)) + return np.stack(rows), counts + + +def _metric_row( + split: Split, + logits: np.ndarray, + intensity: np.ndarray, + probabilities: np.ndarray, + *, + method: str, + seed: int, + scenario: str, +) -> dict[str, Any]: + pred_class = logits.argmax(axis=-1) + y_cls = split.y_cls + y_reg = split.y_reg + confidence = probabilities.max(axis=-1) + correct = (pred_class == y_cls).astype(np.float64) + ece = 0.0 + for left in np.linspace(0.0, 1.0, 16)[:-1]: + right = left + 1.0 / 15.0 + hit = (confidence >= left) & (confidence < right if right < 1.0 else confidence <= right) + if hit.any(): + ece += float(hit.mean() * abs(confidence[hit].mean() - correct[hit].mean())) + one_hot = np.eye(3, dtype=np.float64)[y_cls] + pearson = float(np.corrcoef(y_reg, intensity)[0, 1]) if np.std(y_reg) > 0 and np.std(intensity) > 0 else 0.0 + cm = confusion_matrix(y_cls, pred_class, labels=[0, 1, 2]).tolist() + return { + "method": method, + "seed": seed, + "scenario": scenario, + "samples": len(y_cls), + "accuracy": float(accuracy_score(y_cls, pred_class)), + "macro_f1": float(f1_score(y_cls, pred_class, labels=[0, 1, 2], average="macro", zero_division=0)), + "weighted_f1": float(f1_score(y_cls, pred_class, average="weighted", zero_division=0)), + "negative_recall": float(recall_score(y_cls, pred_class, labels=[0, 1, 2], average=None, zero_division=0)[0]), + "neutral_recall": float(recall_score(y_cls, pred_class, labels=[0, 1, 2], average=None, zero_division=0)[1]), + "positive_recall": float(recall_score(y_cls, pred_class, labels=[0, 1, 2], average=None, zero_division=0)[2]), + "mae": float(mean_absolute_error(y_reg, intensity)), + "rmse": float(math.sqrt(mean_squared_error(y_reg, intensity))), + "pearson": pearson, + "ece_15bin": ece, + "brier_multiclass": float(np.mean(np.sum((probabilities - one_hot) ** 2, axis=-1))), + "confusion_matrix_0_1_2": json.dumps(cm), + } + + +@torch.inference_mode() +def _predict_arrays( + model: nn.Module, + split: Split, + masks: np.ndarray, + device: torch.device, + *, + batch_size: int = BATCH_SIZE, + ati: bool, + details: bool = False, +) -> dict[str, np.ndarray]: + model.eval() + outputs: dict[str, list[np.ndarray]] = {"logits": [], "intensity": [], "probabilities": []} + if details: + outputs.update({"params": [], "baseline": [], "main_effects": [], "pair_effects": []}) + for start in range(0, split.n, batch_size): + end = min(split.n, start + batch_size) + xs = tuple(torch.as_tensor(x[start:end], dtype=torch.float32, device=device) for x in split.x) + mask = torch.as_tensor(masks[start:end], dtype=torch.bool, device=device) + result = model(xs, mask, return_details=details) if ati else model(xs, mask) + logits = result["logits"] + if ati: + probs = result["probabilities"] + intensity = result["intensity"] + if details: + for key in ("params", "baseline", "main_effects", "pair_effects"): + outputs[key].append(result[key].detach().cpu().numpy()) + else: + probs = torch.softmax(logits, dim=-1) + intensity = result["intensity"].clamp(-3.0, 3.0) + outputs["logits"].append(logits.detach().cpu().numpy()) + outputs["probabilities"].append(probs.detach().cpu().numpy()) + outputs["intensity"].append(intensity.detach().cpu().numpy()) + return {key: np.concatenate(values, axis=0) for key, values in outputs.items()} + + +def _loss_on_masks( + model: nn.Module, + split: Split, + masks: np.ndarray, + device: torch.device, + *, + ati: bool, + lambda_interaction: float, + lambda_mask: float, +) -> float: + model.eval() + losses: list[float] = [] + counts: list[int] = [] + with torch.inference_mode(): + for start in range(0, split.n, BATCH_SIZE): + end = min(split.n, start + BATCH_SIZE) + xs = tuple(torch.as_tensor(x[start:end], dtype=torch.float32, device=device) for x in split.x) + mb = torch.as_tensor(masks[start:end], dtype=torch.bool, device=device) + y_cls = torch.as_tensor(split.y_cls[start:end], dtype=torch.long, device=device) + y_reg = torch.as_tensor(split.y_reg[start:end], dtype=torch.float32, device=device) + output = model(xs, mb, return_details=False) if ati else model(xs, mb) + if ati: + loss, _ = task_loss( + output, + y_cls, + y_reg, + lambda_interaction=lambda_interaction, + lambda_mask=0.0, + ) + else: + loss = baseline_loss(output, y_cls, y_reg) + losses.append(float(loss.item())) + counts.append(end - start) + return float(np.average(losses, weights=counts)) + + +def _selection_loss( + model: nn.Module, + valid: Split, + scenarios: dict[str, np.ndarray], + device: torch.device, + *, + ati: bool, + config: ATIConfig | None, +) -> float: + return float( + np.mean( + [ + _loss_on_masks( + model, + valid, + scenarios[key], + device, + ati=ati, + lambda_interaction=config.lambda_interaction if config else 0.0, + lambda_mask=0.0, + ) + for key in SELECTION_SCENARIOS + ] + ) + ) + + +def _save_csv(path: Path, rows: list[dict[str, Any]], *, append: bool = False) -> None: + if not rows: + return + path.parent.mkdir(parents=True, exist_ok=True) + fields = list(dict.fromkeys(key for row in rows for key in row)) + write_header = not (append and path.exists() and path.stat().st_size > 0) + mode = "a" if append else "w" + with path.open(mode, newline="", encoding="utf-8-sig") as stream: + writer = csv.DictWriter(stream, fieldnames=fields, extrasaction="ignore") + if write_header: + writer.writeheader() + writer.writerows(rows) + + +def _train_one( + method: str, + seed: int, + train: Split, + valid: Split, + valid_scenarios: dict[str, np.ndarray], + device: torch.device, + *, + epochs: int, + force: bool, +) -> tuple[nn.Module, dict[str, Any]]: + ati = method in CONFIGS + config = CONFIGS[method] if ati else None + run_dir = EXPERIMENT_ROOT / "models" / method / f"seed_{seed}" + run_dir.mkdir(parents=True, exist_ok=True) + checkpoint_path = run_dir / "model_best.pt" + if checkpoint_path.is_file() and not force: + saved = torch.load(checkpoint_path, map_location=device, weights_only=False) + if saved.get("method") != method or int(saved.get("seed", -1)) != seed: + raise ValueError(f"stale or mismatched checkpoint: {checkpoint_path}") + model = ATIHOModel(tuple(x.shape[-1] for x in train.x), config).to(device) if ati else build_baseline(method, tuple(x.shape[-1] for x in train.x), device) + model.load_state_dict(saved["state_dict"]) + model.eval() + return model, saved + + _seed_everything(seed) + dims = tuple(int(x.shape[-1]) for x in train.x) + model = ATIHOModel(dims, config).to(device) if ati else build_baseline(method, dims, device) + optimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE, weight_decay=WEIGHT_DECAY) + train_x = tuple(torch.as_tensor(x, dtype=torch.float32, device=device) for x in train.x) + train_cls = torch.as_tensor(train.y_cls, dtype=torch.long, device=device) + train_reg = torch.as_tensor(train.y_reg, dtype=torch.float32, device=device) + train_base_mask = torch.as_tensor(train.mask, dtype=torch.bool, device=device) + order_rng = np.random.default_rng(seed + 809) + orders = [order_rng.permutation(train.n) for _ in range(epochs)] + history: list[dict[str, Any]] = [] + mask_counts: Counter[str] = Counter() + best_selection = math.inf + best_epoch = 0 + stale = 0 + start_time = time.perf_counter() + + for epoch in range(1, epochs + 1): + model.train() + current_masks, current_counts = _training_masks(train, seed, epoch) + mask_counts.update(current_counts) + losses: list[float] = [] + order = orders[epoch - 1] + for start in range(0, train.n, BATCH_SIZE): + index_np = order[start : start + BATCH_SIZE] + index = torch.as_tensor(index_np, dtype=torch.long, device=device) + mb = torch.as_tensor(current_masks[index_np], dtype=torch.bool, device=device) + xs = tuple(x.index_select(0, index) for x in train_x) + output = model(xs, mb, return_details=False) if ati else model(xs, mb) + if ati: + loss, loss_parts = task_loss( + output, + train_cls.index_select(0, index), + train_reg.index_select(0, index), + lambda_interaction=config.lambda_interaction, + lambda_mask=config.lambda_mask, + mask_target=train_base_mask.index_select(0, index), + ) + else: + loss = baseline_loss(output, train_cls.index_select(0, index), train_reg.index_select(0, index)) + loss_parts = {"total": loss} + optimizer.zero_grad(set_to_none=True) + loss.backward() + nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) + optimizer.step() + losses.append(float(loss.detach().item())) + + selection = _selection_loss( + model, valid, valid_scenarios, device, ati=ati, config=config + ) + clean = _loss_on_masks( + model, + valid, + valid.mask, + device, + ati=ati, + lambda_interaction=config.lambda_interaction if config else 0.0, + lambda_mask=0.0, + ) + row = { + "method": method, + "seed": seed, + "epoch": epoch, + "train_loss": float(np.mean(losses)), + "valid_selection_loss": selection, + "valid_clean_loss": clean, + "lambda_interaction": config.lambda_interaction if config else 0.0, + "lambda_mask": config.lambda_mask if config else 0.0, + } + history.append(row) + print( + f"[{method} seed={seed}] epoch={epoch:02d} train={row['train_loss']:.4f} " + f"valid_selection={selection:.4f} clean={clean:.4f}", + flush=True, + ) + if selection < best_selection - 1e-4: + best_selection = selection + best_epoch = epoch + stale = 0 + state = { + "method": method, + "seed": seed, + "dims": dims, + "steps": train.steps, + "config": config.to_dict() if config else None, + "state_dict": model.state_dict(), + "best_epoch": best_epoch, + "best_selection_loss": best_selection, + "protocol": "official Q2 V2 unaligned_50 Relative-Progress; train-only robust scaler; video-disjoint validation", + } + torch.save(state, checkpoint_path) + else: + stale += 1 + if stale >= PATIENCE: + break + + saved = torch.load(checkpoint_path, map_location=device, weights_only=False) + model.load_state_dict(saved["state_dict"]) + model.eval() + _save_csv(run_dir / "training_history.csv", history) + (run_dir / "training_manifest.json").write_text( + json.dumps( + { + "method": method, + "seed": seed, + "best_epoch": best_epoch, + "best_selection_loss": best_selection, + "elapsed_seconds": time.perf_counter() - start_time, + "batch_size": BATCH_SIZE, + "epoch_limit": epochs, + "patience": PATIENCE, + "optimizer": "AdamW", + "learning_rate": LEARNING_RATE, + "weight_decay": WEIGHT_DECAY, + "gradient_clip_norm": 1.0, + "training_mask_rates": list(TRAIN_RATES), + "training_mask_patterns": list(TRAIN_MODES), + "training_mask_seed_base": TRAIN_MASK_SEED, + "same_orders_and_masks_across_methods_for_same_seed": True, + "config": config.to_dict() if config else {"model_config": MODEL_CONFIG}, + "history": history, + "mask_counts": dict(mask_counts), + }, + ensure_ascii=False, + indent=2, + ), + encoding="utf-8", + ) + return model, saved + + +def _evaluate_job( + method: str, + seed: int, + model: nn.Module, + valid: Split, + scenarios: dict[str, np.ndarray], + device: torch.device, +) -> list[dict[str, Any]]: + ati = method in CONFIGS + rows: list[dict[str, Any]] = [] + selected = {"clean": valid.mask} + selected.update({key: scenarios[key] for key in SELECTION_SCENARIOS if key != "0.0/none"}) + for name, masks in selected.items(): + predictions = _predict_arrays(model, valid, masks, device, ati=ati) + rows.append( + _metric_row( + valid, + predictions["logits"], + predictions["intensity"], + predictions["probabilities"], + method=method, + seed=seed, + scenario=name, + ) + ) + return rows + + +def _write_root_manifest(data_meta: dict[str, Any], device: torch.device, epochs: int) -> None: + EXPERIMENT_ROOT.mkdir(parents=True, exist_ok=True) + try: + git_sha = subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=PROJECT_ROOT, text=True, stderr=subprocess.DEVNULL + ).strip() + except Exception: + git_sha = None + info: dict[str, Any] = { + "experiment": "ATI–HO Q3 staged training and structural attribution audit", + "created_utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "device": str(device), + "torch_version": torch.__version__, + "cuda_available": torch.cuda.is_available(), + "cuda_version": torch.version.cuda, + "gpu": torch.cuda.get_device_name(0) if torch.cuda.is_available() else None, + "python": platform.python_version(), + "seeds": list(MODEL_SEEDS), + "epochs_max": epochs, + "training_protocol": { + "batch_size": BATCH_SIZE, + "early_stopping_patience": PATIENCE, + "optimizer": "AdamW", + "learning_rate": LEARNING_RATE, + "weight_decay": WEIGHT_DECAY, + "gradient_clip_norm": 1.0, + "training_mask_rates": list(TRAIN_RATES), + "training_mask_patterns": list(TRAIN_MODES), + "validation_selection_scenarios": list(SELECTION_SCENARIOS), + "held_out_attachment4_touched_during_training": False, + }, + "ati_output": { + "parameter_vector": "3 centered class logits + r_negative + r_positive", + "intensity": "negative/positive magnitudes are 3*sigmoid(r); neutral class is exactly zero", + "loss": "cross entropy + conditional magnitude SmoothL1 + 0.2*Huber(delta=0.25) + configured regularizers", + "baseline_checkpoint_reuse": "No: retrain B0 and B1 on the fixed ATI split/mask schedule because existing Q2 checkpoints differ in seeds, batch size, and schedule.", + "calibration_temperature": 1.0, + }, + "data": data_meta, + } + (EXPERIMENT_ROOT / "run_manifest.json").write_text( + json.dumps(info, ensure_ascii=False, indent=2), encoding="utf-8" + ) + + +def run_stage1(train: Split, valid: Split, scenarios: dict[str, np.ndarray], device: torch.device, epochs: int, force: bool) -> None: + (EXPERIMENT_ROOT / "stage1_complete.json").unlink(missing_ok=True) + rows: list[dict[str, Any]] = [] + models: dict[str, nn.Module] = {} + for method in ("A0", "A1", "A2", "A3", "D0"): + model, saved = _train_one(method, 42, train, valid, scenarios, device, epochs=epochs, force=force) + models[method] = model + rows.extend(_evaluate_job(method, 42, model, valid, scenarios, device)) + print(f"[{method}] best_epoch={saved['best_epoch']} selected_loss={saved['best_selection_loss']:.5f}", flush=True) + _save_csv(EXPERIMENT_ROOT / "validation_results.csv", rows) + candidates = [] + for method in ("A0", "A1", "A2", "A3"): + checkpoint = torch.load(EXPERIMENT_ROOT / "models" / method / "seed_42" / "model_best.pt", map_location="cpu", weights_only=False) + candidates.append({ + "method": method, + "best_selection_loss": float(checkpoint["best_selection_loss"]), + "best_epoch": int(checkpoint["best_epoch"]), + }) + candidates.sort(key=lambda row: row["best_selection_loss"]) + choice = candidates[0]["method"] + candidate_doc = { + "stage1_candidates": candidates, + "provisional_selected_candidate": choice, + "selection_rule": "lowest fixed four-scenario ATI task loss on the locked official validation split; seed 42 only in Stage I", + } + (EXPERIMENT_ROOT / "provisional_candidate.json").write_text( + json.dumps(candidate_doc, ensure_ascii=False, indent=2), encoding="utf-8" + ) + smoke_count = min(16, valid.n) + smoke_xs = tuple(torch.as_tensor(x[:smoke_count], dtype=torch.float32, device=device) for x in valid.x) + smoke_mask = torch.as_tensor(valid.mask[:smoke_count], dtype=torch.bool, device=device) + structural_rows = [] + for method, model in models.items(): + report = structural_audit(model, smoke_xs, smoke_mask) + row = {"method": method, "seed": 42, "samples": smoke_count, **report} + row["pair_single_missing_anchor_max_abs"] = json.dumps( + report["pair_single_missing_anchor_max_abs"], sort_keys=True + ) + structural_rows.append(row) + if not report["checks_pass"]: + raise RuntimeError(f"Stage I structural audit failed for {method}: {report}") + if method == "D0" and not report["unanchored_control_detected_leakage"]: + raise RuntimeError("D0 unanchored diagnostic did not expose the expected missing-modality leakage") + _save_csv(EXPERIMENT_ROOT / "structural_audit.csv", structural_rows) + shapley = exact_shapley_audit([models[choice]], smoke_xs, smoke_mask, batch_size=32) + if not np.asarray(shapley["class_pass"]).all(): + raise RuntimeError( + f"Stage I analytic-vs-exact Shapley audit failed for {choice}: " + f"max_abs={float(np.max(shapley['class_abs_error'])):.8g}" + ) + shapley_rows = [] + for index in range(smoke_count): + shapley_rows.append( + { + "sample_index": index, + "method": choice, + "target_class": int(shapley["target_class"][index]), + "runner_up_class": int(shapley["runner_up_class"][index]), + "analytic_T": float(shapley["analytic_class"][index, 0]), + "analytic_A": float(shapley["analytic_class"][index, 1]), + "analytic_V": float(shapley["analytic_class"][index, 2]), + "exact_T": float(shapley["exact_class"][index, 0]), + "exact_A": float(shapley["exact_class"][index, 1]), + "exact_V": float(shapley["exact_class"][index, 2]), + "max_abs_error": float(shapley["class_abs_error"][index].max()), + "pass": bool(shapley["class_pass"][index].all()), + } + ) + _save_csv(EXPERIMENT_ROOT / "shapley_audit_seed42_smoke.csv", shapley_rows) + (EXPERIMENT_ROOT / "stage1_complete.json").write_text( + json.dumps( + { + "models": ["A0", "A1", "A2", "A3", "D0"], + "seed": 42, + "structural_audit_pass": True, + "analytic_vs_exact_shapley_pass": True, + "shapley_smoke_samples": smoke_count, + "selected_candidate": choice, + }, + indent=2, + ), + encoding="utf-8", + ) + + +def run_stage2(train: Split, valid: Split, scenarios: dict[str, np.ndarray], device: torch.device, epochs: int, force: bool) -> None: + provisional_path = EXPERIMENT_ROOT / "provisional_candidate.json" + if not provisional_path.is_file(): + raise FileNotFoundError("run Stage I before Stage II; provisional_candidate.json is missing") + selected = json.loads(provisional_path.read_text(encoding="utf-8"))["provisional_selected_candidate"] + key_ablations = { + "A0": ["A1", "A2"], + "A1": ["A0", "A2"], + "A2": ["A0", "A1"], + "A3": ["A2", "A1"], + }[selected] + methods = list(dict.fromkeys([selected, *key_ablations])) + rows: list[dict[str, Any]] = [] + for method in (EARLYCONCAT, MOFE7_MLP, *methods): + for seed in MODEL_SEEDS: + model, saved = _train_one(method, seed, train, valid, scenarios, device, epochs=epochs, force=force) + rows.extend(_evaluate_job(method, seed, model, valid, scenarios, device)) + print( + f"[{method} seed={seed}] best_epoch={saved['best_epoch']} " + f"selected_loss={saved['best_selection_loss']:.5f}", + flush=True, + ) + _save_csv(EXPERIMENT_ROOT / "validation_results.csv", rows, append=True) + stage2 = { + "selected_candidate": selected, + "key_ablations": key_ablations, + "baseline_methods": [EARLYCONCAT, MOFE7_MLP], + "seeds": list(MODEL_SEEDS), + "baseline_checkpoints_retrained": True, + "all_selection_uses_locked_validation_only": True, + } + (EXPERIMENT_ROOT / "stage2_complete.json").write_text( + json.dumps(stage2, ensure_ascii=False, indent=2), encoding="utf-8" + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description="Train ATI–HO and compatible Q3 baselines.") + parser.add_argument("--phase", choices=("stage1", "stage2", "all"), default="all") + parser.add_argument("--device", default="auto") + parser.add_argument("--epochs", type=int, default=EPOCH_LIMIT) + parser.add_argument("--force", action="store_true") + args = parser.parse_args() + device = torch.device("cuda" if args.device == "auto" and torch.cuda.is_available() else "cpu" if args.device == "auto" else args.device) + train, valid, _stats, data_meta = load_training_data() + scenarios = make_scenarios(valid, SCENARIO_SEED) + _write_root_manifest(data_meta, device, args.epochs) + print( + f"ATI–HO protocol: train={train.n} valid={valid.n} groups=" + f"{data_meta['train_source_video_groups']}/{data_meta['valid_source_video_groups']} " + f"dims={data_meta['dimensions']} device={device}", + flush=True, + ) + if args.phase in {"stage1", "all"}: + run_stage1(train, valid, scenarios, device, args.epochs, args.force) + if args.phase in {"stage2", "all"}: + run_stage2(train, valid, scenarios, device, args.epochs, args.force) + + +if __name__ == "__main__": + main() diff --git a/submit/final/q3/run_experiments.py b/submit/final/q3/run_experiments.py new file mode 100644 index 0000000..09ddbe5 --- /dev/null +++ b/submit/final/q3/run_experiments.py @@ -0,0 +1,1413 @@ +"""Run the first Q3 explanation comparison on the official Attachment 4 cases. + +The runner reuses the Q2 EarlyConcat and MoFE checkpoints and their train-only +robust scaler. E1 and E2 are two explanation views of the same MoFE model. +""" +from __future__ import annotations + +import argparse +import csv +import gc +import hashlib +import itertools +import json +import math +import pickle +import re +import shutil +import subprocess +import time +from pathlib import Path +from typing import Any, Iterable, Mapping + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +import torch +from scipy.stats import spearmanr +from sklearn.metrics import accuracy_score, f1_score, mean_absolute_error, mean_squared_error + +from ..adapter import Q1AlignmentAdapter, adapt_official_split +from ..data_paths import ATTACHMENT2, ATTACHMENT4, DATA_ROOT, PROJECT_ROOT +from ..model.early_concat import AlignedFusionModel +from ..model.mofe import EXPERT_NAMES, MixtureOfFusionExperts +from ..q2.deep_learning.q2.data import MODALITIES + + +SEED = 20260924 +CLASS_NAMES = ("negative", "neutral", "positive") +MODEL_VARIANTS = ( + ("E0_EarlyConcat", "early_concat", "exact Shapley + multiscale occlusion"), + ("E1_MoFE_Router", "mofe", "router weights, tested by counterfactual deletion"), + ("E2_MoFE_Shapley", "mofe", "exact Shapley + multiscale occlusion"), +) +COALITIONS = tuple( + frozenset(c) + for size in range(4) + for c in itertools.combinations(range(3), size) +) +WINDOWS = (1, 3, 5) +EXPLANATION_FRACTION = 0.10 +DEFAULT_RUN = PROJECT_ROOT / "experiments" / "q2" / "unaligned_deep_two_b128" +DEFAULT_EARLY = DEFAULT_RUN / "models" / "B0_early_concat" / "seed_20260924" / "model_best.pt" +DEFAULT_MOFE = DEFAULT_RUN / "models" / "B5_mofe_mlp" / "seed_20260924" / "model_best.pt" +DEFAULT_SCALER = DEFAULT_RUN / "unaligned_50_robust_stats.npz" + +try: + from transformers import AutoTokenizer +except ImportError: # Token highlighting degrades gracefully; inference has no HF dependency. + AutoTokenizer = None # type: ignore[assignment,misc] + + +def _sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for block in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(block) + return digest.hexdigest() + + +def _decode(value: Any) -> str: + if isinstance(value, bytes): + return value.decode("utf-8", errors="replace") + if isinstance(value, np.bytes_): + return bytes(value).decode("utf-8", errors="replace") + if isinstance(value, np.ndarray): + if value.shape == (): + return _decode(value.item()) + return " ".join(_decode(x) for x in value.reshape(-1)) + return str(value) + + +def _safe_name(value: str) -> str: + name = re.sub(r"[^A-Za-z0-9_.-]+", "_", value).strip("_.") + return name or "sample" + + +def _write_csv(path: Path, rows: list[dict[str, Any]]) -> None: + if not rows: + return + path.parent.mkdir(parents=True, exist_ok=True) + fields = list(dict.fromkeys(key for row in rows for key in row)) + with path.open("w", newline="", encoding="utf-8-sig") as stream: + writer = csv.DictWriter(stream, fieldnames=fields, extrasaction="ignore") + writer.writeheader() + writer.writerows(rows) + + +def _write_json(path: Path, payload: Any) -> None: + path.write_text(json.dumps(payload, ensure_ascii=False, indent=2, allow_nan=False) + "\n", encoding="utf-8") + + +def _optional_float(value: Any) -> float | None: + value = float(value) + return value if math.isfinite(value) else None + + +def exact_shapley(values: Mapping[frozenset[int], float], player_count: int = 3) -> np.ndarray: + """Exact Shapley values for a small finite coalition game.""" + players = set(range(player_count)) + result = np.zeros(player_count, dtype=np.float64) + denom = math.factorial(player_count) + for player in range(player_count): + others = sorted(players - {player}) + for size in range(player_count): + for coalition_tuple in itertools.combinations(others, size): + coalition = frozenset(coalition_tuple) + weight = math.factorial(size) * math.factorial(player_count - size - 1) / denom + result[player] += weight * ( + values[coalition | {player}] - values[coalition] + ) + return result + + +def exact_pair_interactions( + values: Mapping[frozenset[int], float], player_count: int = 3 +) -> dict[tuple[int, int], float]: + """Shapley interaction index with the standard one-half pair coefficient.""" + result: dict[tuple[int, int], float] = {} + for first, second in itertools.combinations(range(player_count), 2): + remaining = sorted(set(range(player_count)) - {first, second}) + total = 0.0 + for size in range(len(remaining) + 1): + for coalition_tuple in itertools.combinations(remaining, size): + coalition = frozenset(coalition_tuple) + weight = ( + math.factorial(size) + * math.factorial(player_count - size - 2) + / (2 * math.factorial(player_count - 1)) + ) + total += weight * ( + values[coalition | {first, second}] + - values[coalition | {first}] + - values[coalition | {second}] + + values[coalition] + ) + result[(first, second)] = float(total) + return result + + +def _load_scaler(path: Path) -> tuple[tuple[np.ndarray, ...], tuple[np.ndarray, ...]]: + with np.load(path, allow_pickle=False) as archive: + centers = tuple(archive[f"{name}_center"].astype(np.float32) for name in MODALITIES) + scales = tuple(archive[f"{name}_scale"].astype(np.float32) for name in MODALITIES) + if any(np.any(~np.isfinite(scale)) or np.any(scale <= 0) for scale in scales): + raise ValueError(f"invalid robust scaler: {path}") + return centers, scales + + +def _scale_features( + features: tuple[np.ndarray, ...], + mask: np.ndarray, + centers: tuple[np.ndarray, ...], + scales: tuple[np.ndarray, ...], +) -> tuple[np.ndarray, ...]: + result: list[np.ndarray] = [] + for index, source in enumerate(features): + values = (np.asarray(source, dtype=np.float32) - centers[index]) / scales[index] + values = np.nan_to_num(values, nan=0.0, posinf=0.0, neginf=0.0) + visible = mask[:, index] if mask.ndim == 2 else mask[:, :, index] + values *= visible[..., None] + result.append(values.astype(np.float32, copy=False)) + return tuple(result) + + +def _attachment4_paths(version: str) -> tuple[Path, Path]: + if version != "unaligned_50": + raise ValueError("Q3 uses the official unaligned_50 Attachment 4 features") + inner = ATTACHMENT4 / "附件4-可解释专项视频样本与特征文件" + version_dir = inner / "未对齐版本" + # The submitted archive places videos inside the feature-version folder. + video_dir = version_dir / "videos" + if not video_dir.is_dir(): + video_dir = inner / "videos" + if not version_dir.is_dir(): + raise FileNotFoundError(f"Attachment 4 feature folder not found: {version_dir}") + return version_dir, video_dir + + +def _media_duration(path: Path | None) -> float | None: + if path is None or not path.is_file(): + return None + try: + proc = subprocess.run( + [ + "ffprobe", "-v", "error", "-show_entries", "format=duration", + "-of", "default=noprint_wrappers=1:nokey=1", str(path), + ], + check=True, + capture_output=True, + text=True, + timeout=20, + ) + duration = float(proc.stdout.strip()) + return duration if duration > 0 else None + except (OSError, subprocess.SubprocessError, ValueError): + return None + + +def _read_attachment4(version: str) -> tuple[list[dict[str, Any]], dict[str, str]]: + version_dir, video_dir = _attachment4_paths(version) + paths = sorted(version_dir.glob("*.pkl"), key=lambda p: p.name) + if len(paths) != 20: + raise FileNotFoundError(f"expected 20 Attachment 4 cases, found {len(paths)} in {version_dir}") + video_by_stem = {p.stem: p for p in video_dir.glob("*.mp4")} if video_dir.is_dir() else {} + adapter = Q1AlignmentAdapter(target_steps=50) + cases: list[dict[str, Any]] = [] + for path in paths: + with path.open("rb") as stream: + raw = pickle.load(stream, encoding="latin1") + case_id = _decode(raw.get("id", path.stem)).strip() or path.stem + text_bert = np.asarray(raw["text_bert"], dtype=np.int64) + if text_bert.ndim == 3 and text_bert.shape[0] == 1: + text_bert = text_bert[0] + if text_bert.shape != (3, 50): + raise ValueError(f"{path.name}: expected text_bert shape (3,50), got {text_bert.shape}") + record = { + "id": case_id, + "sequence_order_verified": True, + "attention_mask": text_bert[1].astype(bool), + "text": np.asarray(raw["text"], dtype=np.float32), + "audio": np.asarray(raw["audio"], dtype=np.float32), + "vision": np.asarray(raw["vision"], dtype=np.float32), + "audio_length": int(np.asarray(raw["audio_lengths"]).reshape(-1)[0]), + "vision_length": int(np.asarray(raw["vision_lengths"]).reshape(-1)[0]), + } + aligned = adapter.align(record, mode="relative") + mask = np.stack([aligned.observed[name] for name in MODALITIES], axis=-1) + features = tuple(aligned.features[name].astype(np.float32) for name in MODALITIES) + video = video_by_stem.get(path.stem) or video_by_stem.get(case_id) + try: + video_relative = video.resolve().relative_to(DATA_ROOT).as_posix() if video is not None else "" + except ValueError: + video_relative = str(video.resolve()) if video is not None else "" + duration = _media_duration(video) + cases.append( + { + "case_id": case_id, + "source_file": path, + "source_sha256": _sha256(path), + "transcript": _decode(raw.get("raw_text", "")), + "text_bert": text_bert, + "features": features, + "mask": mask, + "target_intervals": aligned.target_intervals.astype(np.float32), + "provenance": aligned.provenance, + "video_file": video, + "video_path": video_relative, + "video_duration_sec": duration, + "input_audit": { + "case_id": case_id, + "source_file": path.name, + "source_sha256": _sha256(path), + "coordinate_mode": aligned.metadata["coordinate_mode"], + "physical_time_alignment": False, + "audio_reported_length": record["audio_length"], + "vision_reported_length": record["vision_length"], + "audio_length_conflict": bool(aligned.provenance["audio"].length_conflict), + "vision_length_conflict": bool(aligned.provenance["vision"].length_conflict), + "text_visible_target_slots": int(aligned.observed["text"].sum()), + "audio_visible_target_slots": int(aligned.observed["audio"].sum()), + "vision_visible_target_slots": int(aligned.observed["vision"].sum()), + "source_video": video_relative, + "source_video_duration_sec": _optional_float(duration) if video else None, + }, + } + ) + return cases, {"version_dir": str(version_dir), "video_dir": str(video_dir)} + + +def _build_model(kind: str, dims: tuple[int, int, int], checkpoint_path: Path, device: torch.device) -> torch.nn.Module: + checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True) + stored_dims = tuple(int(x) for x in checkpoint.get("dims", ())) + if stored_dims != dims: + raise ValueError(f"{checkpoint_path} expects {stored_dims}, adapter produced {dims}") + if kind == "early_concat": + model: torch.nn.Module = AlignedFusionModel("concat", dims=dims) + elif kind == "mofe": + model = MixtureOfFusionExperts(dims=dims) + else: + raise ValueError(f"unknown model kind: {kind}") + model.load_state_dict(checkpoint["state_dict"], strict=True) + model.to(device).eval() + return model + + +def _model_output( + model: torch.nn.Module, + xs: tuple[torch.Tensor, ...], + mask: torch.Tensor, +) -> dict[str, torch.Tensor]: + model.eval() + with torch.inference_mode(): + return model(xs, mask) + + +def _batched_output( + model: torch.nn.Module, + features: tuple[np.ndarray, ...], + masks: np.ndarray, + device: torch.device, + batch_size: int, +) -> tuple[np.ndarray, np.ndarray]: + logits: list[np.ndarray] = [] + intensity: list[np.ndarray] = [] + model.eval() + with torch.inference_mode(): + for start in range(0, len(masks), batch_size): + end = min(start + batch_size, len(masks)) + xs = tuple(torch.as_tensor(x[start:end], dtype=torch.float32, device=device) for x in features) + batch_mask = torch.as_tensor(masks[start:end], dtype=torch.bool, device=device) + output = model(xs, batch_mask) + logits.append(output["logits"].float().cpu().numpy()) + intensity.append(output["intensity"].float().cpu().numpy()) + return np.concatenate(logits), np.concatenate(intensity) + + +def _single_input_mask_outputs( + model: torch.nn.Module, + features: tuple[np.ndarray, ...], + masks: np.ndarray, + device: torch.device, + batch_size: int, +) -> tuple[np.ndarray, np.ndarray]: + logits: list[np.ndarray] = [] + intensity: list[np.ndarray] = [] + model.eval() + with torch.inference_mode(): + for start in range(0, len(masks), batch_size): + end = min(start + batch_size, len(masks)) + count = end - start + xs = tuple( + torch.as_tensor(np.repeat(x[None], count, axis=0), dtype=torch.float32, device=device) + for x in features + ) + output = model(xs, torch.as_tensor(masks[start:end], dtype=torch.bool, device=device)) + logits.append(output["logits"].float().cpu().numpy()) + intensity.append(output["intensity"].float().cpu().numpy()) + return np.concatenate(logits), np.concatenate(intensity).reshape(-1) + + +def _coalition_outputs( + model: torch.nn.Module, + features: tuple[np.ndarray, ...], + base_mask: np.ndarray, + device: torch.device, +) -> tuple[np.ndarray, np.ndarray]: + masks = np.repeat(base_mask[None, :, :], len(COALITIONS), axis=0) + for index, coalition in enumerate(COALITIONS): + for modality in range(3): + if modality not in coalition: + masks[index, :, modality] = False + xs = tuple( + torch.as_tensor(np.repeat(x[None, :, :], len(COALITIONS), axis=0), dtype=torch.float32, device=device) + for x in features + ) + with torch.inference_mode(): + output = model(xs, torch.as_tensor(masks, dtype=torch.bool, device=device)) + return output["logits"].float().cpu().numpy(), output["intensity"].float().cpu().numpy().reshape(-1) + + +def _values_for_task( + coalition_logits: np.ndarray, + coalition_intensity: np.ndarray, + predicted_class: int, +) -> tuple[dict[frozenset[int], float], dict[frozenset[int], float]]: + class_values = {coalition: float(coalition_logits[i, predicted_class]) for i, coalition in enumerate(COALITIONS)} + reg_values = {coalition: float(coalition_intensity[i]) for i, coalition in enumerate(COALITIONS)} + return class_values, reg_values + + +def _shares(values: np.ndarray) -> np.ndarray: + denominator = float(np.abs(values).sum()) + return np.abs(values) / denominator if denominator > 1e-12 else np.zeros_like(values) + + +def _source_rows(case: dict[str, Any], modality_index: int, slots: Iterable[int]) -> list[int]: + provenance = case["provenance"][MODALITIES[modality_index]] + rows: set[int] = set() + for slot in slots: + rows.update(int(x) for x in provenance.source_weights.getrow(int(slot)).indices) + return sorted(rows) + + +def _decode_text_rows(case: dict[str, Any], source_rows: list[int], tokenizer: Any) -> tuple[str, str]: + ids = np.asarray(case["text_bert"][0], dtype=np.int64) + usable = [index for index in source_rows if 0 <= index < len(ids)] + if not usable: + return case["transcript"], "whole_transcript_no_token_overlap" + if tokenizer is None: + return case["transcript"], "whole_transcript_tokenizer_unavailable" + selected = [int(ids[index]) for index in usable if int(ids[index]) not in tokenizer.all_special_ids] + if not selected: + return case["transcript"], "whole_transcript_special_tokens_only" + return tokenizer.decode(selected, skip_special_tokens=True, clean_up_tokenization_spaces=True), "bert_token_ids" + + +def _local_occlusion( + model: torch.nn.Module, + features: tuple[np.ndarray, ...], + base_mask: np.ndarray, + full_logit: float, + full_intensity: float, + predicted_class: int, + device: torch.device, + batch_size: int, +) -> tuple[dict[tuple[int, int], dict[str, float]], dict[int, np.ndarray]]: + masks: list[np.ndarray] = [] + meta: list[tuple[int, int, int]] = [] + for modality in range(3): + for width in WINDOWS: + radius = width // 2 + for slot in np.flatnonzero(base_mask[:, modality]).tolist(): + masked = base_mask.copy() + left, right = max(0, slot - radius), min(base_mask.shape[0], slot + radius + 1) + masked[left:right, modality] = False + masks.append(masked) + meta.append((modality, slot, width)) + class_drop: dict[tuple[int, int, int], float] = {} + reg_drop: dict[tuple[int, int, int], float] = {} + if masks: + logits, intensity = _single_input_mask_outputs(model, features, np.stack(masks), device, batch_size) + for key, logit_row, regression in zip(meta, logits, intensity): + class_drop[key] = float(full_logit - logit_row[predicted_class]) + reg_drop[key] = float(full_intensity - regression) + local: dict[tuple[int, int], dict[str, float]] = {} + scale_maps = {width: np.full((3, base_mask.shape[0]), np.nan, dtype=np.float32) for width in WINDOWS} + for modality in range(3): + for slot in np.flatnonzero(base_mask[:, modality]).tolist(): + row: dict[str, float] = {} + for width in WINDOWS: + row[f"class_logit_drop_w{width}"] = class_drop.get((modality, slot, width), 0.0) + row[f"intensity_drop_w{width}"] = reg_drop.get((modality, slot, width), 0.0) + scale_maps[width][modality, slot] = row[f"class_logit_drop_w{width}"] + row["class_logit_drop_multiscale"] = float(np.mean([row[f"class_logit_drop_w{w}"] for w in WINDOWS])) + row["intensity_drop_multiscale"] = float(np.mean([row[f"intensity_drop_w{w}"] for w in WINDOWS])) + local[(modality, slot)] = row + return local, scale_maps + + +def _router_profile(model: torch.nn.Module, features: tuple[np.ndarray, ...], mask: np.ndarray, device: torch.device) -> tuple[dict[str, Any], np.ndarray]: + xs = tuple(torch.as_tensor(x[None], dtype=torch.float32, device=device) for x in features) + tensor_mask = torch.as_tensor(mask[None], dtype=torch.bool, device=device) + output = _model_output(model, xs, tensor_mask) + alpha = output.get("alpha") + utility = output.get("utility") + if alpha is None or utility is None: + raise TypeError("router profile requested for a model without MoFE router outputs") + alpha_np = alpha[0].float().cpu().numpy() + utility_np = utility[0].float().cpu().numpy() + valid_slots = mask.any(axis=-1) + if valid_slots.any(): + expert_mean = alpha_np[valid_slots].mean(axis=0) + exposure = utility_np[valid_slots].mean(axis=0) + else: + expert_mean = np.zeros(len(EXPERT_NAMES), dtype=np.float32) + exposure = np.zeros(3, dtype=np.float32) + exposure_share = _shares(exposure) + row: dict[str, Any] = {} + for name, value in zip(EXPERT_NAMES, expert_mean): + row[f"router_expert_{name}"] = float(value) + for index, name in enumerate(MODALITIES): + row[f"router_{name}_exposure"] = float(exposure[index]) + row[f"router_{name}_share"] = float(exposure_share[index]) + return row, utility_np.T + + +def _spearman(x: np.ndarray, y: np.ndarray) -> float | None: + if len(x) < 2 or np.allclose(x, x[0]) or np.allclose(y, y[0]): + return None + return _optional_float(spearmanr(x, y).statistic) + + +def _scale_stability(scale_maps: dict[int, np.ndarray], mask: np.ndarray) -> dict[str, float | None]: + results: dict[str, float | None] = {} + values = [scale_maps[width][mask.T] for width in WINDOWS] + for (left, right), a, b in zip(((1, 3), (1, 5), (3, 5)), (values[0], values[0], values[1]), (values[1], values[2], values[2])): + results[f"spearman_w{left}_w{right}"] = _spearman(a, b) + finite = [value for value in results.values() if value is not None] + results["mean_scale_spearman"] = float(np.mean(finite)) if finite else None + return results + + +def _ranked_positions(profile: np.ndarray, mask: np.ndarray) -> list[tuple[int, int]]: + candidates = [ + (modality, slot, float(profile[modality, slot])) + for modality in range(3) + for slot in range(mask.shape[0]) + if mask[slot, modality] and math.isfinite(float(profile[modality, slot])) + ] + return [(m, t) for m, t, _ in sorted(candidates, key=lambda item: (-item[2], item[0], item[1]))] + + +def _faithfulness( + model: torch.nn.Module, + features: tuple[np.ndarray, ...], + base_mask: np.ndarray, + full_logit: float, + predicted_class: int, + importance: np.ndarray, + device: torch.device, +) -> dict[str, Any]: + positions = _ranked_positions(importance, base_mask) + n = len(positions) + deletion_fractions = (0.0, 0.1, 0.2, 0.3, 0.5, 0.7) + masks: list[np.ndarray] = [] + tags: list[tuple[str, float, int]] = [] + for fraction in deletion_fractions: + count = min(n, int(math.ceil(n * fraction))) if fraction else 0 + masked = base_mask.copy() + for modality, slot in positions[:count]: + masked[slot, modality] = False + masks.append(masked) + tags.append(("delete", fraction, count)) + for fraction in (0.1, 0.2, 0.3): + count = min(n, max(1, int(math.ceil(n * fraction)))) if n else 0 + retained = np.zeros_like(base_mask) + for modality, slot in positions[:count]: + retained[slot, modality] = True + masks.append(retained) + tags.append(("retain", fraction, count)) + logits, _ = _single_input_mask_outputs(model, features, np.stack(masks), device, batch_size=32) + row: dict[str, Any] = {"observed_cells": n, "ranking_cells": n} + drops: list[float] = [] + for tag, logit_row in zip(tags, logits): + operation, fraction, count = tag + score = float(logit_row[predicted_class]) + change = float(full_logit - score) + if operation == "delete": + row[f"comprehensiveness_delete_{int(fraction * 100)}pct"] = change + row[f"deleted_cells_{int(fraction * 100)}pct"] = count + drops.append(change) + else: + row[f"sufficiency_gap_retain_{int(fraction * 100)}pct"] = change + row[f"sufficiency_abs_gap_retain_{int(fraction * 100)}pct"] = abs(change) + row[f"retained_cells_{int(fraction * 100)}pct"] = count + row["deletion_auc_0_70_mean_logit_drop"] = float( + np.trapezoid(np.asarray(drops, dtype=np.float64), x=np.asarray(deletion_fractions)) / 0.7 + ) + return row + + +def _segment_profile( + profile: np.ndarray, + mask: np.ndarray, + case: dict[str, Any], + variant: str, + tokenizer: Any, + output_dir: Path, + extract_frames: bool, +) -> list[dict[str, Any]]: + segments: list[dict[str, Any]] = [] + for modality in range(3): + visible = np.flatnonzero(mask[:, modality]) + if not len(visible): + continue + k = max(1, int(math.ceil(len(visible) * EXPLANATION_FRACTION))) + chosen = sorted( + visible.tolist(), + key=lambda slot: (-abs(float(profile[modality, slot])), slot), + )[:k] + groups: list[list[int]] = [] + for slot in sorted(chosen): + if groups and slot == groups[-1][-1] + 1: + groups[-1].append(slot) + else: + groups.append([slot]) + groups.sort(key=lambda group: (-sum(abs(float(profile[modality, t])) for t in group), group[0])) + for rank, group in enumerate(groups[:2], start=1): + rows = _source_rows(case, modality, group) + start = float(case["target_intervals"][group[0], 0]) + end = float(case["target_intervals"][group[-1], 1]) + duration = case["video_duration_sec"] + start_sec = start * duration if duration is not None else None + end_sec = end * duration if duration is not None else None + if modality == 0: + evidence, text_method = _decode_text_rows(case, rows, tokenizer) + elif modality == 1: + evidence = f"unaligned audio feature rows {min(rows) if rows else 0}–{max(rows) if rows else -1}; review the linked source clip at the estimated relative span" + text_method = "feature_row_provenance" + else: + evidence = f"unaligned vision feature rows {min(rows) if rows else 0}–{max(rows) if rows else -1}; candidate frame time is estimated from relative progress" + text_method = "feature_row_provenance" + signed = float(sum(float(profile[modality, t]) for t in group)) + strength = float(sum(abs(float(profile[modality, t])) for t in group)) + if variant == "E1_MoFE_Router": + direction = "router_activity_not_signed_contribution" + score_semantics = "internal router utility; not a prediction effect" + else: + direction = "supports_predicted_class" if signed > 0 else ("opposes_predicted_class" if signed < 0 else "neutral") + score_semantics = "predicted-class logit drop after local occlusion" + frame_path = "" + if modality == 2 and extract_frames and case["video_file"] is not None and start_sec is not None and end_sec is not None: + midpoint = (start_sec + end_sec) / 2.0 + if duration: + midpoint = min(max(midpoint, 0.0), max(0.0, duration - 0.05)) + name = f"{_safe_name(variant)}_{_safe_name(case['case_id'])}_vision_{rank}.jpg" + target = output_dir / "evidence_frames" / name + target.parent.mkdir(parents=True, exist_ok=True) + try: + subprocess.run( + ["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-ss", f"{midpoint:.4f}", + "-i", str(case["video_file"]), "-frames:v", "1", "-vf", "scale=640:-2", str(target)], + check=True, + capture_output=True, + timeout=30, + ) + frame_path = target.relative_to(output_dir).as_posix() + except (OSError, subprocess.SubprocessError): + frame_path = "" + segments.append( + { + "variant": variant, + "case_id": case["case_id"], + "modality": MODALITIES[modality], + "rank_within_modality": rank, + "slot_start_0based": group[0], + "slot_end_exclusive": group[-1] + 1, + "relative_progress_start": start, + "relative_progress_end": end, + "source_row_start": min(rows) if rows else 0, + "source_row_end_exclusive": max(rows) + 1 if rows else 0, + "local_score_sum_signed": signed, + "local_score_mass": strength, + "direction": direction, + "score_semantics": score_semantics, + "evidence_text": evidence, + "text_evidence_method": text_method, + "source_video": case["video_path"], + "video_time_start_sec_estimate": start_sec, + "video_time_end_sec_estimate": end_sec, + "video_time_basis": "relative progress times clip duration; approximate, not a physical feature timestamp", + "candidate_frame": frame_path, + } + ) + return segments + + +def _plot_profile( + path: Path, + profile: np.ndarray, + mask: np.ndarray, + title: str, + router: bool = False, +) -> None: + values = np.asarray(profile, dtype=np.float32).copy() + values[~mask.T] = np.nan + fig, ax = plt.subplots(figsize=(11, 2.8), constrained_layout=True) + cmap = plt.get_cmap("viridis" if router else "coolwarm").copy() + cmap.set_bad("#d8d8d8") + if router: + vmax = max(float(np.nanmax(values)) if np.isfinite(values).any() else 0.0, 1e-6) + image = ax.imshow(np.ma.masked_invalid(values), aspect="auto", interpolation="nearest", cmap=cmap, vmin=0, vmax=vmax) + else: + bound = max(float(np.nanmax(np.abs(values))) if np.isfinite(values).any() else 0.0, 1e-6) + image = ax.imshow(np.ma.masked_invalid(values), aspect="auto", interpolation="nearest", cmap=cmap, vmin=-bound, vmax=bound) + ax.set_yticks(range(3), ("Text", "Audio", "Vision")) + ax.set_xticks(range(0, 50, 5), range(0, 50, 5)) + ax.set_xlabel("Relative progress bin (0-based)") + ax.set_title(title) + fig.colorbar(image, ax=ax, label="router utility" if router else "predicted-class logit drop") + path.parent.mkdir(parents=True, exist_ok=True) + fig.savefig(path, dpi=150) + plt.close(fig) + + +def _metrics(y_cls: np.ndarray, y_reg: np.ndarray, logits: np.ndarray, intensity: np.ndarray) -> dict[str, Any]: + predicted = logits.argmax(axis=-1) + score = np.clip(intensity.reshape(-1), -3.0, 3.0) + pearson = float(np.corrcoef(y_reg, score)[0, 1]) if np.std(y_reg) > 0 and np.std(score) > 0 else None + return { + "n": int(len(y_cls)), + "accuracy": float(accuracy_score(y_cls, predicted)), + "macro_f1": float(f1_score(y_cls, predicted, labels=[0, 1, 2], average="macro", zero_division=0)), + "mae": float(mean_absolute_error(y_reg, score)), + "rmse": float(math.sqrt(mean_squared_error(y_reg, score))), + "pearson": pearson, + } + + +def _load_validation( + path: Path, + centers: tuple[np.ndarray, ...], + scales: tuple[np.ndarray, ...], +) -> tuple[list[str], tuple[np.ndarray, ...], np.ndarray, np.ndarray, np.ndarray, dict[str, Any]]: + if not path.is_file(): + raise FileNotFoundError(f"Attachment 2 unaligned_50 pickle not found: {path}") + print(f"Loading the official validation split from {path} ...", flush=True) + with path.open("rb") as stream: + raw = pickle.load(stream, encoding="latin1") + part = raw["valid"] + raw_ids = part["id"] + ids = [_decode(value) for value in raw_ids] + y_cls = np.asarray(part["classification_labels"], dtype=np.int64).reshape(-1) + y_reg = np.asarray(part["regression_labels"], dtype=np.float32).reshape(-1) + features_dict, mask, audit = adapt_official_split(part) + features = tuple(features_dict[name] for name in MODALITIES) + del part, raw, raw_ids, features_dict + gc.collect() + features = _scale_features(features, mask, centers, scales) + return ids, features, mask, y_cls, y_reg, audit + + +def _validation_errors( + ids: list[str], + features: tuple[np.ndarray, ...], + mask: np.ndarray, + y_cls: np.ndarray, + y_reg: np.ndarray, + model_rows: dict[str, tuple[torch.nn.Module, np.ndarray, np.ndarray]], + device: torch.device, + batch_size: int, + output_dir: Path, +) -> tuple[dict[str, Any], int]: + predictions: list[dict[str, Any]] = [] + error_indices: set[int] = set() + validation_metrics: dict[str, Any] = {} + cache: dict[str, tuple[np.ndarray, np.ndarray]] = {} + for model_name, (model, _unused_logits, _unused_intensity) in model_rows.items(): + logits, intensity = _batched_output(model, features, mask, device, batch_size) + cache[model_name] = logits, intensity + validation_metrics[model_name] = _metrics(y_cls, y_reg, logits, intensity) + predicted = logits.argmax(axis=-1) + abs_error = np.abs(y_reg - np.clip(intensity, -3.0, 3.0)) + misses = np.flatnonzero(predicted != y_cls) + error_indices.update(int(i) for i in misses) + top_reg = np.argsort(-abs_error)[: min(20, len(abs_error))] + error_indices.update(int(i) for i in top_reg) + for i, sample_id in enumerate(ids): + probabilities = torch.softmax(torch.as_tensor(logits[i]), dim=-1).numpy() + predictions.append( + { + "model": model_name, + "sample_id": sample_id, + "true_class": int(y_cls[i]), + "true_class_name": CLASS_NAMES[int(y_cls[i])], + "predicted_class": int(predicted[i]), + "predicted_class_name": CLASS_NAMES[int(predicted[i])], + "classification_correct": bool(predicted[i] == y_cls[i]), + "true_intensity": float(y_reg[i]), + "predicted_intensity": float(intensity[i]), + "absolute_intensity_error": float(abs_error[i]), + "p_negative": float(probabilities[0]), + "p_neutral": float(probabilities[1]), + "p_positive": float(probabilities[2]), + } + ) + _write_csv(output_dir / "validation_predictions.csv", predictions) + _write_json(output_dir / "validation_metrics.json", validation_metrics) + + sorted_errors = sorted(error_indices, key=lambda i: (y_cls[i] == cache["early_concat"][0][i].argmax(), -abs(float(y_reg[i] - cache["early_concat"][1][i])))) + error_rows: list[dict[str, Any]] = [] + for i in sorted_errors: + for model_name, (logits, intensity) in cache.items(): + predicted = int(logits[i].argmax()) + error_rows.append( + { + "model": model_name, + "sample_id": ids[i], + "true_class_name": CLASS_NAMES[int(y_cls[i])], + "predicted_class_name": CLASS_NAMES[predicted], + "classification_correct": bool(predicted == y_cls[i]), + "true_intensity": float(y_reg[i]), + "predicted_intensity": float(intensity[i]), + "absolute_intensity_error": float(abs(float(y_reg[i] - intensity[i]))), + "error_selection": "classification error or top-20 intensity error", + } + ) + _write_csv(output_dir / "validation_errors.csv", error_rows) + + attribution: list[dict[str, Any]] = [] + for i in sorted_errors[:50]: + for model_name, (model, _, _) in model_rows.items(): + coal_logits, coal_intensity = _coalition_outputs(model, tuple(x[i] for x in features), mask[i], device) + predicted = int(cache[model_name][0][i].argmax()) + true = int(y_cls[i]) + if predicted != true: + alternative = predicted + target_name = "predicted_logit_minus_true_logit" + class_values = { + coalition: float(coal_logits[j, alternative] - coal_logits[j, true]) + for j, coalition in enumerate(COALITIONS) + } + else: + alternatives = [c for c in range(3) if c != true] + alternative = max(alternatives, key=lambda c: float(cache[model_name][0][i, c])) + target_name = "true_logit_minus_best_alternative" + class_values = { + coalition: float(coal_logits[j, true] - coal_logits[j, alternative]) + for j, coalition in enumerate(COALITIONS) + } + reg_values = {coalition: float(coal_intensity[j]) for j, coalition in enumerate(COALITIONS)} + class_phi = exact_shapley(class_values) + reg_phi = exact_shapley(reg_values) + row: dict[str, Any] = { + "model": model_name, + "sample_id": ids[i], + "true_class_name": CLASS_NAMES[true], + "predicted_class_name": CLASS_NAMES[predicted], + "classification_correct": bool(predicted == true), + "true_intensity": float(y_reg[i]), + "predicted_intensity": float(cache[model_name][1][i]), + "absolute_intensity_error": float(abs(float(y_reg[i] - cache[model_name][1][i]))), + "classification_margin_target": target_name, + "classification_margin_phi_sum_residual": float(class_phi.sum() - (class_values[COALITIONS[-1]] - class_values[frozenset()])), + "regression_shapley_sum_residual": float(reg_phi.sum() - (reg_values[COALITIONS[-1]] - reg_values[frozenset()])), + } + for m, name in enumerate(MODALITIES): + row[f"class_margin_phi_{name}"] = float(class_phi[m]) + row[f"class_margin_share_{name}"] = float(_shares(class_phi)[m]) + row[f"regression_phi_{name}"] = float(reg_phi[m]) + row[f"regression_share_{name}"] = float(_shares(reg_phi)[m]) + row["class_margin_dominant_modality"] = MODALITIES[int(np.argmax(np.abs(class_phi)))] + attribution.append(row) + _write_csv(output_dir / "validation_error_attribution.csv", attribution) + return validation_metrics, len(sorted_errors) + + +def _q2_reference_metrics(path: Path) -> dict[str, dict[str, float]]: + if not path.is_file(): + return {} + rows: dict[str, dict[str, float]] = {} + with path.open("r", newline="", encoding="utf-8-sig") as stream: + for row in csv.DictReader(stream): + if row.get("split") != "official_valid": + continue + if row.get("model") not in ("EarlyConcat", "MoFE-7"): + continue + rows[row["model"]] = { + key: float(row[key]) for key in ("accuracy", "macro_f1", "mae", "rmse", "pearson") + } + return rows + + +def _make_cards( + output_dir: Path, + cases: list[dict[str, Any]], + results: list[dict[str, Any]], + shapley_by_key: dict[tuple[str, str], dict[str, Any]], + interaction_by_key: dict[tuple[str, str], dict[str, Any]], + router_by_id: dict[str, dict[str, Any]], + segment_rows: list[dict[str, Any]], + faithfulness_rows: list[dict[str, Any]], +) -> str: + case_by_id = {case["case_id"]: case for case in cases} + segments_by: dict[tuple[str, str], list[dict[str, Any]]] = {} + for row in segment_rows: + segments_by.setdefault((str(row["variant"]), str(row["case_id"])), []).append(row) + faith_by: dict[tuple[str, str], dict[str, Any]] = { + (str(row["variant"]), str(row["case_id"])): row for row in faithfulness_rows + } + result_by = {(str(row["variant"]), str(row["case_id"])): row for row in results} + card_paths: dict[tuple[str, str], Path] = {} + confidences = [float(row["confidence"]) for row in results if row["variant"] == "E2_MoFE_Shapley"] + median_conf = float(np.median(confidences)) + typical_id = min( + (str(row["case_id"]) for row in results if row["variant"] == "E2_MoFE_Shapley"), + key=lambda cid: abs(result_by[("E2_MoFE_Shapley", cid)]["confidence"] - median_conf), + ) + for variant, _, _ in MODEL_VARIANTS: + folder = output_dir / "explanation_cards" / variant + folder.mkdir(parents=True, exist_ok=True) + for case in cases: + cid = str(case["case_id"]) + item = result_by[(variant, cid)] + base_model = "early_concat" if variant == "E0_EarlyConcat" else "mofe" + shap = shapley_by_key[(base_model, cid)] + inter = interaction_by_key[(base_model, cid)] + router = router_by_id.get(cid, {}) + faithful = faith_by[(variant, cid)] + lines = [ + f"# Q3 explanation card — {variant} — {cid}", + "", + "## Prediction", + "", + f"- Polarity: **{item['predicted_class_name']}**", + f"- Intensity: {item['predicted_sentiment']:+.3f}", + f"- Predicted-class confidence: {item['confidence']:.3f}", + f"- Source clip: {case['video_path'] or 'video not found'}", + "", + ] + if variant == "E1_MoFE_Router": + lines += [ + "## Router profile (intrinsic routing signal, not prediction contribution)", + "", + "| Modality | Router exposure share | Exact Shapley absolute share |", + "|---|---:|---:|", + ] + for name in MODALITIES: + lines.append( + f"| {name} | {router.get(f'router_{name}_share', 0.0):.3f} | {shap[f'class_share_{name}']:.3f} |" + ) + lines += [ + "", + f"Router–Shapley Spearman: {router.get('router_shapley_spearman')}; top modality agreement: {router.get('router_shapley_top1_agreement')}.", + "Router values describe mixture routing. The counterfactual scores below test whether that routing signal tracks model behavior.", + "", + ] + else: + lines += [ + "## Exact modality Shapley", + "", + "Positive values support the predicted class logit; negative values oppose it. Shares use absolute values and are model decision contributions, not real-world emotion importance.", + "", + "| Modality | Class logit contribution | Absolute share | Intensity contribution | Absolute share |", + "|---|---:|---:|---:|---:|", + ] + for name in MODALITIES: + lines.append( + f"| {name} | {shap[f'class_phi_{name}']:+.4f} | {shap[f'class_share_{name}']:.3f} | {shap[f'regression_phi_{name}']:+.4f} | {shap[f'regression_share_{name}']:.3f} |" + ) + lines += [ + "", + f"Shapley completeness residuals: class {shap['class_completeness_residual']:.2e}, intensity {shap['regression_completeness_residual']:.2e}.", + "## Pairwise Shapley interaction", + "", + "| Pair | Class logit | Intensity |", + "|---|---:|---:|", + f"| Text + Audio | {inter['class_interaction_TA']:+.4f} | {inter['regression_interaction_TA']:+.4f} |", + f"| Text + Vision | {inter['class_interaction_TV']:+.4f} | {inter['regression_interaction_TV']:+.4f} |", + f"| Audio + Vision | {inter['class_interaction_AV']:+.4f} | {inter['regression_interaction_AV']:+.4f} |", + "", + ] + lines += [ + "## Local evidence segments", + "", + "Local counterfactual scores are the predicted-class logit difference after hiding a 1/3/5-bin window, averaged equally across the three scales. E1 ranks its router utility and is evaluated separately.", + "", + ] + evidence = segments_by.get((variant, cid), []) + for row in evidence: + segment = ( + f"{row['modality']} bins {row['slot_start_0based']}–{int(row['slot_end_exclusive']) - 1} " + f"(relative progress {row['relative_progress_start']:.3f}–{row['relative_progress_end']:.3f})" + ) + if row.get("video_time_start_sec_estimate") is not None: + segment += f", estimated clip interval {row['video_time_start_sec_estimate']:.2f}–{row['video_time_end_sec_estimate']:.2f}s" + lines.append(f"- **{segment}** — {row['direction']}; evidence: {row['evidence_text']}") + if row.get("candidate_frame"): + lines.append(f" - Candidate frame: ![estimated visual evidence](../../{row['candidate_frame']})") + if not evidence: + lines.append("- No observed local evidence cells for this sample.") + image_rel = Path("..") / ".." / "evidence_profiles" / variant / f"{_safe_name(cid)}.png" + lines += [ + "", + f"![Local evidence profile]({image_rel.as_posix()})", + "", + "## Faithfulness checks", + "", + f"- Comprehensiveness after deleting the top 10% / 30% cells: {faithful['comprehensiveness_delete_10pct']:+.4f} / {faithful['comprehensiveness_delete_30pct']:+.4f} predicted-class logit.", + f"- Sufficiency gap when retaining the top 10% / 30%: {faithful['sufficiency_gap_retain_10pct']:+.4f} / {faithful['sufficiency_gap_retain_30pct']:+.4f}. Smaller absolute gaps are better.", + f"- Mean deletion logit drop over 0–70% deletion: {faithful['deletion_auc_0_70_mean_logit_drop']:+.4f}.", + "", + "## Provenance limit", + "", + "Attachment 4 supplies unaligned feature sequences without word/audio/frame timestamps. Feature rows are traced to source rows and normalized progress. Clip-time estimates multiply that progress by the video duration; they are approximate review locations, not physical alignment timestamps.", + "", + "Occlusion and Shapley values describe this trained model's response to masked inputs. They do not establish causal effects or prove the emotion expressed by a person.", + "", + "## Transcript", + "", + case["transcript"] or "(not supplied)", + "", + ] + path = folder / f"{_safe_name(cid)}.md" + path.write_text("\n".join(lines), encoding="utf-8") + card_paths[(variant, cid)] = path + source = card_paths[("E2_MoFE_Shapley", typical_id)] + (output_dir / "typical_explanation_card.md").write_text(source.read_text(encoding="utf-8"), encoding="utf-8") + return typical_id + + +def _evaluate_case( + case: dict[str, Any], + models: dict[str, torch.nn.Module], + device: torch.device, + explanation_batch_size: int, + tokenizer: Any, + output_dir: Path, + extract_frames: bool, +) -> dict[str, Any]: + xs = case["features"] + mask = case["mask"] + result: dict[str, Any] = {"case_id": case["case_id"], "models": {}, "variant_rows": []} + local_by_model: dict[str, dict[tuple[int, int], dict[str, float]]] = {} + scale_by_model: dict[str, dict[int, np.ndarray]] = {} + router_by_model: dict[str, dict[str, Any]] = {} + for model_name, model in models.items(): + outputs = _model_output( + model, + tuple(torch.as_tensor(x[None], dtype=torch.float32, device=device) for x in xs), + torch.as_tensor(mask[None], dtype=torch.bool, device=device), + ) + logits = outputs["logits"][0].float().cpu().numpy() + intensity = float(outputs["intensity"][0].float().cpu().item()) + probabilities = torch.softmax(outputs["logits"][0].float(), dim=-1).cpu().numpy() + predicted = int(logits.argmax()) + class_values, reg_values = _values_for_task( + *_coalition_outputs(model, xs, mask, device), + predicted, + ) + class_phi = exact_shapley(class_values) + reg_phi = exact_shapley(reg_values) + class_share = _shares(class_phi) + reg_share = _shares(reg_phi) + class_interactions = exact_pair_interactions(class_values) + reg_interactions = exact_pair_interactions(reg_values) + modal_row: dict[str, Any] = { + "base_model": model_name, + "case_id": case["case_id"], + "predicted_class_name": CLASS_NAMES[predicted], + "class_value_empty": class_values[frozenset()], + "class_value_full": class_values[COALITIONS[-1]], + "regression_value_empty": reg_values[frozenset()], + "regression_value_full": reg_values[COALITIONS[-1]], + "class_completeness_residual": float(class_phi.sum() - (class_values[COALITIONS[-1]] - class_values[frozenset()])), + "regression_completeness_residual": float(reg_phi.sum() - (reg_values[COALITIONS[-1]] - reg_values[frozenset()])), + } + for index, name in enumerate(MODALITIES): + modal_row[f"class_phi_{name}"] = float(class_phi[index]) + modal_row[f"class_share_{name}"] = float(class_share[index]) + modal_row[f"regression_phi_{name}"] = float(reg_phi[index]) + modal_row[f"regression_share_{name}"] = float(reg_share[index]) + interaction_row: dict[str, Any] = {"base_model": model_name, "case_id": case["case_id"]} + for key, label in (((0, 1), "TA"), ((0, 2), "TV"), ((1, 2), "AV")): + interaction_row[f"class_interaction_{label}"] = class_interactions[key] + interaction_row[f"regression_interaction_{label}"] = reg_interactions[key] + local, scale_maps = _local_occlusion( + model, xs, mask, float(logits[predicted]), intensity, predicted, device, explanation_batch_size + ) + local_by_model[model_name] = local + scale_by_model[model_name] = scale_maps + router_row: dict[str, Any] | None = None + if model_name == "mofe": + router_row, router_map = _router_profile(model, xs, mask, device) + router_row["case_id"] = case["case_id"] + router_row["router_shapley_spearman"] = _spearman( + np.asarray([router_row[f"router_{name}_share"] for name in MODALITIES]), class_share + ) + router_row["router_shapley_top1_agreement"] = bool( + np.argmax([router_row[f"router_{name}_share"] for name in MODALITIES]) == np.argmax(class_share) + ) + router_by_model[model_name] = router_row + result.setdefault("router_local", {})[model_name] = router_map + result["models"][model_name] = { + "logits": logits, + "probabilities": probabilities, + "predicted_class": predicted, + "intensity": intensity, + "modal_row": modal_row, + "interaction_row": interaction_row, + "router_row": router_row, + "local": local, + "scale_maps": scale_maps, + } + result["variant_rows"].append( + { + "variant": "E0_EarlyConcat" if model_name == "early_concat" else "E1_MoFE_Router", + "base_model": model_name, + "case_id": case["case_id"], + "predicted_class": predicted, + "predicted_class_name": CLASS_NAMES[predicted], + "predicted_sentiment": intensity, + "confidence": float(probabilities[predicted]), + "p_negative": float(probabilities[0]), + "p_neutral": float(probabilities[1]), + "p_positive": float(probabilities[2]), + "source_video": case["video_path"], + "explanation_method": "exact Shapley + multiscale occlusion" if model_name == "early_concat" else "router profile; not itself a counterfactual contribution", + } + ) + if model_name == "mofe": + result["variant_rows"].append( + { + "variant": "E2_MoFE_Shapley", + "base_model": model_name, + "case_id": case["case_id"], + "predicted_class": predicted, + "predicted_class_name": CLASS_NAMES[predicted], + "predicted_sentiment": intensity, + "confidence": float(probabilities[predicted]), + "p_negative": float(probabilities[0]), + "p_neutral": float(probabilities[1]), + "p_positive": float(probabilities[2]), + "source_video": case["video_path"], + "explanation_method": "exact Shapley + multiscale occlusion", + } + ) + result["local_by_model"] = local_by_model + result["scale_by_model"] = scale_by_model + result["router_by_model"] = router_by_model + return result + + +def run(args: argparse.Namespace) -> None: + out_dir = args.output_dir.expanduser().resolve() + out_dir.mkdir(parents=True, exist_ok=True) + remaining = [p for p in out_dir.iterdir() if p.name != ".gitkeep"] + if remaining and not args.resume: + raise FileExistsError(f"output directory is not empty; choose a new path: {out_dir}") + if remaining: + print(f"Reusing existing Q3 output directory after a partial run: {out_dir}", flush=True) + started = time.time() + for path in (args.early_checkpoint, args.mofe_checkpoint, args.scaler): + if not path.is_file(): + raise FileNotFoundError(f"Q2 model artifact not found: {path}") + centers, scales = _load_scaler(args.scaler) + cases, input_locations = _read_attachment4(args.attachment4_version) + dims = tuple(int(x.shape[-1]) for x in cases[0]["features"]) + device = torch.device("cuda" if args.device == "auto" and torch.cuda.is_available() else ("cpu" if args.device == "auto" else args.device)) + if device.type == "cuda": + torch.cuda.manual_seed_all(SEED) + torch.set_float32_matmul_precision("high") + torch.set_num_threads(4) + models = { + "early_concat": _build_model("early_concat", dims, args.early_checkpoint, device), + "mofe": _build_model("mofe", dims, args.mofe_checkpoint, device), + } + for case in cases: + case["features"] = _scale_features(case["features"], case["mask"], centers, scales) + tokenizer = None + if AutoTokenizer is not None: + try: + tokenizer = AutoTokenizer.from_pretrained("google-bert/bert-base-uncased", use_fast=True, local_files_only=True) + except Exception: + tokenizer = None + + prediction_rows: list[dict[str, Any]] = [] + shapley_rows: list[dict[str, Any]] = [] + interaction_rows: list[dict[str, Any]] = [] + local_rows: list[dict[str, Any]] = [] + router_rows: list[dict[str, Any]] = [] + router_local_rows: list[dict[str, Any]] = [] + faithfulness_rows: list[dict[str, Any]] = [] + segment_rows: list[dict[str, Any]] = [] + profiles_for_summary: dict[str, list[np.ndarray]] = {variant[0]: [] for variant in MODEL_VARIANTS} + result_for_cards: list[dict[str, Any]] = [] + shapley_by_key: dict[tuple[str, str], dict[str, Any]] = {} + interaction_by_key: dict[tuple[str, str], dict[str, Any]] = {} + router_by_id: dict[str, dict[str, Any]] = {} + validation_models: dict[str, tuple[torch.nn.Module, np.ndarray, np.ndarray]] = {} + + for index, case in enumerate(cases, start=1): + print(f"Q3 Attachment 4: explaining {case['case_id']} ({index}/{len(cases)})", flush=True) + evaluated = _evaluate_case( + case, models, device, args.explanation_batch_size, tokenizer, out_dir, not args.no_frames + ) + for model_name in ("early_concat", "mofe"): + model_result = evaluated["models"][model_name] + shap_row = model_result["modal_row"] + interaction_row = model_result["interaction_row"] + shapley_rows.append(shap_row) + interaction_rows.append(interaction_row) + shapley_by_key[(model_name, case["case_id"])] = shap_row + interaction_by_key[(model_name, case["case_id"])] = interaction_row + if model_name == "mofe": + router_by_id[case["case_id"]] = model_result["router_row"] + router_rows.append(model_result["router_row"]) + for (modality, slot), scores in model_result["local"].items(): + provenance_rows = _source_rows(case, modality, [slot]) + interval = case["target_intervals"][slot] + duration = case["video_duration_sec"] + local_rows.append( + { + "base_model": model_name, + "case_id": case["case_id"], + "modality": MODALITIES[modality], + "slot_0based": slot, + "relative_progress_start": float(interval[0]), + "relative_progress_end": float(interval[1]), + **scores, + "source_row_start": min(provenance_rows) if provenance_rows else 0, + "source_row_end_exclusive": max(provenance_rows) + 1 if provenance_rows else 0, + "source_video": case["video_path"], + "video_time_start_sec_estimate": float(interval[0] * duration) if duration else None, + "video_time_end_sec_estimate": float(interval[1] * duration) if duration else None, + } + ) + stability = _scale_stability(model_result["scale_maps"], case["mask"]) + local_map = np.full((3, 50), np.nan, dtype=np.float32) + for (modality, slot), scores in model_result["local"].items(): + local_map[modality, slot] = scores["class_logit_drop_multiscale"] + if model_name == "early_concat": + variant = "E0_EarlyConcat" + importance = np.nan_to_num(local_map, nan=0.0) + router_display = False + else: + variant = "E2_MoFE_Shapley" + importance = np.nan_to_num(local_map, nan=0.0) + router_display = False + faith = _faithfulness( + models[model_name], case["features"], case["mask"], + float(model_result["logits"][model_result["predicted_class"]]), + int(model_result["predicted_class"]), np.abs(importance), device, + ) + faith.update( + { + "variant": variant, + "case_id": case["case_id"], + "scale_stability_mean_spearman": stability["mean_scale_spearman"], + "scale_stability_w1_w3": stability["spearman_w1_w3"], + "scale_stability_w1_w5": stability["spearman_w1_w5"], + "scale_stability_w3_w5": stability["spearman_w3_w5"], + } + ) + faithfulness_rows.append(faith) + segment_rows.extend( + _segment_profile(importance, case["mask"], case, variant, tokenizer, out_dir, not args.no_frames) + ) + profiles_for_summary[variant].append(importance) + if model_name == "mofe": + router_map = evaluated["router_local"]["mofe"] + router_profile = np.nan_to_num(router_map, nan=0.0) + router_faith = _faithfulness( + models["mofe"], case["features"], case["mask"], + float(model_result["logits"][model_result["predicted_class"]]), + int(model_result["predicted_class"]), router_profile, device, + ) + router_faith.update({"variant": "E1_MoFE_Router", "case_id": case["case_id"]}) + faithfulness_rows.append(router_faith) + segment_rows.extend( + _segment_profile(router_profile, case["mask"], case, "E1_MoFE_Router", tokenizer, out_dir, not args.no_frames) + ) + profiles_for_summary["E1_MoFE_Router"].append(router_profile) + _plot_profile( + out_dir / "evidence_profiles" / "E1_MoFE_Router" / f"{_safe_name(case['case_id'])}.png", + router_profile, case["mask"], f"E1 MoFE router utility — {case['case_id']}", router=True, + ) + for modality in range(3): + for slot in range(50): + if case["mask"][slot, modality]: + router_local_rows.append( + { + "case_id": case["case_id"], + "modality": MODALITIES[modality], + "slot_0based": slot, + "router_utility": float(router_map[modality, slot]), + "relative_progress_start": float(case["target_intervals"][slot, 0]), + "relative_progress_end": float(case["target_intervals"][slot, 1]), + } + ) + display_profile = router_profile if model_name == "mofe" and router_display else importance + _plot_profile( + out_dir / "evidence_profiles" / variant / f"{_safe_name(case['case_id'])}.png", + display_profile, case["mask"], f"{variant} — {case['case_id']}", router=router_display, + ) + + result_for_cards.extend(evaluated["variant_rows"]) + for row in evaluated["variant_rows"]: + prediction_rows.append(row) + validation_models["early_concat"] = ( + models["early_concat"], + evaluated["models"]["early_concat"]["logits"], + np.asarray([evaluated["models"]["early_concat"]["intensity"]]), + ) + validation_models["mofe"] = ( + models["mofe"], + evaluated["models"]["mofe"]["logits"], + np.asarray([evaluated["models"]["mofe"]["intensity"]]), + ) + + _write_csv(out_dir / "attachment4_predictions.csv", prediction_rows) + _write_csv(out_dir / "attachment4_modal_shapley.csv", shapley_rows) + _write_csv(out_dir / "attachment4_pairwise_interactions.csv", interaction_rows) + _write_csv(out_dir / "attachment4_local_evidence.csv", local_rows) + _write_csv(out_dir / "attachment4_router_profiles.csv", router_rows) + _write_csv(out_dir / "attachment4_router_local_evidence.csv", router_local_rows) + _write_csv(out_dir / "attachment4_evidence_segments.csv", segment_rows) + _write_csv(out_dir / "faithfulness_by_sample.csv", faithfulness_rows) + typical_id = _make_cards( + out_dir, cases, result_for_cards, shapley_by_key, interaction_by_key, router_by_id, segment_rows, faithfulness_rows + ) + if not args.skip_validation: + validation_path = args.validation_data.expanduser().resolve() + valid = _load_validation(validation_path, centers, scales) + validation_metrics, validation_error_count = _validation_errors( + valid[0], valid[1], valid[2], valid[3], valid[4], + validation_models, device, args.validation_batch_size, out_dir, + ) + del valid + else: + validation_metrics, validation_error_count = {}, 0 + + reference = _q2_reference_metrics(args.q2_validation_reference.expanduser().resolve()) + summary_rows: list[dict[str, Any]] = [] + for variant, model_name, method in MODEL_VARIANTS: + variant_faith = [row for row in faithfulness_rows if row["variant"] == variant] + mean = lambda key: float(np.mean([float(row[key]) for row in variant_faith if row.get(key) is not None])) if any(row.get(key) is not None for row in variant_faith) else None + val_name = "EarlyConcat" if model_name == "early_concat" else "MoFE-7" + q2_metrics = reference.get(val_name, {}) + q3_val = validation_metrics.get(model_name, {}) + summary_rows.append( + { + "variant": variant, + "backbone": "EarlyConcat + BiGRU" if model_name == "early_concat" else "MoFE-7 + MLP Router", + "explanation_method": method, + "validation_accuracy": q3_val.get("accuracy", q2_metrics.get("accuracy")), + "validation_macro_f1": q3_val.get("macro_f1", q2_metrics.get("macro_f1")), + "validation_mae": q3_val.get("mae", q2_metrics.get("mae")), + "validation_rmse": q3_val.get("rmse", q2_metrics.get("rmse")), + "validation_pearson": q3_val.get("pearson", q2_metrics.get("pearson")), + "attachment4_cases": len(cases), + "comprehensiveness_delete_10pct_mean": mean("comprehensiveness_delete_10pct"), + "comprehensiveness_delete_30pct_mean": mean("comprehensiveness_delete_30pct"), + "sufficiency_abs_gap_retain_10pct_mean": mean("sufficiency_abs_gap_retain_10pct"), + "sufficiency_abs_gap_retain_30pct_mean": mean("sufficiency_abs_gap_retain_30pct"), + "deletion_auc_0_70_mean_logit_drop": mean("deletion_auc_0_70_mean_logit_drop"), + "scale_stability_mean_spearman": mean("scale_stability_mean_spearman"), + "router_shapley_mean_spearman": ( + float(np.mean([row["router_shapley_spearman"] for row in router_rows if row.get("router_shapley_spearman") is not None])) + if model_name == "mofe" and any(row.get("router_shapley_spearman") is not None for row in router_rows) + else None + ), + "router_shapley_top1_agreement_rate": ( + float(np.mean([bool(row["router_shapley_top1_agreement"]) for row in router_rows])) + if model_name == "mofe" else None + ), + } + ) + _write_csv(out_dir / "q3_method_comparison.csv", summary_rows) + + completeness = [ + abs(float(row[key])) + for row in shapley_rows + for key in ("class_completeness_residual", "regression_completeness_residual") + ] + if completeness and max(completeness) > 1e-4: + raise AssertionError(f"exact Shapley completeness check failed: max residual {max(completeness)}") + manifest = { + "experiment": "Q3 first-round hierarchical counterfactual evidence attribution", + "created_at_unix": time.time(), + "elapsed_seconds": time.time() - started, + "seed": SEED, + "device": str(device), + "attachment4": input_locations, + "attachment4_version": args.attachment4_version, + "attachment4_cases": len(cases), + "coordinate_mode": "relative normalized progress", + "physical_time_alignment": False, + "time_mapping_limit": "estimated clip seconds equal normalized progress times video duration; original unaligned rows have no physical timestamps", + "models": { + "E0_EarlyConcat": {"checkpoint": str(args.early_checkpoint), "sha256": _sha256(args.early_checkpoint)}, + "E1_E2_MoFE": {"checkpoint": str(args.mofe_checkpoint), "sha256": _sha256(args.mofe_checkpoint)}, + }, + "scaler": {"path": str(args.scaler), "sha256": _sha256(args.scaler), "fit": "Q2 official training rows only"}, + "shapley": { + "coalitions": [sorted(x) for x in COALITIONS], + "class_value": "full-input predicted-class logit, fixed class across coalitions", + "regression_value": "predicted intensity", + "exact_enumeration": True, + "max_completeness_residual": max(completeness) if completeness else None, + }, + "interaction": "pairwise Shapley interaction index; positive values indicate synergistic logit/intensity interaction under this convention", + "local_evidence": {"method": "leave out a 1, 3, or 5-bin contiguous window from one modality", "windows": list(WINDOWS), "scale_weights": [1 / 3] * 3}, + "router_note": "MoFE router exposure is an internal routing summary, not prediction contribution; compare its rank with exact Shapley and deletion faithfulness.", + "faithfulness": { + "score": "fixed predicted-class logit", + "comprehensiveness": "full score minus score after deleting top-ranked cells", + "sufficiency": "full score minus score with only top-ranked cells retained", + "deletion_auc_fraction_range": [0.0, 0.7], + "mask_training_rates": [0.0, 0.1, 0.3, 0.5, 0.7], + "limit": "isolated sparse masks may still differ from the contiguous masks used in training; 90% deletion/10% retention is not claimed as in-distribution", + }, + "validation_metrics": validation_metrics, + "validation_error_examples": validation_error_count, + "q2_validation_reference": reference, + "tokenizer_available": tokenizer is not None, + "outputs": [ + "attachment4_predictions.csv", "attachment4_modal_shapley.csv", + "attachment4_pairwise_interactions.csv", "attachment4_local_evidence.csv", + "attachment4_router_profiles.csv", "attachment4_router_local_evidence.csv", + "attachment4_evidence_segments.csv", "faithfulness_by_sample.csv", + "q3_method_comparison.csv", "explanation_cards/", "evidence_profiles/", + "evidence_frames/", "typical_explanation_card.md", + ] + ([] if args.skip_validation else ["validation_metrics.json", "validation_predictions.csv", "validation_errors.csv", "validation_error_attribution.csv"]), + "typical_explanation_case": typical_id, + } + _write_json(out_dir / "run_manifest.json", manifest) + print(f"Q3 complete: {len(cases)} Attachment 4 cases; outputs saved under {out_dir}", flush=True) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--attachment4-version", choices=("unaligned_50",), default="unaligned_50") + parser.add_argument("--early-checkpoint", type=Path, default=DEFAULT_EARLY) + parser.add_argument("--mofe-checkpoint", type=Path, default=DEFAULT_MOFE) + parser.add_argument("--scaler", type=Path, default=DEFAULT_SCALER) + parser.add_argument("--validation-data", type=Path, default=ATTACHMENT2 / "unaligned_50.pkl") + parser.add_argument("--q2-validation-reference", type=Path, default=PROJECT_ROOT / "output" / "q2" / "comparison_validation.csv") + parser.add_argument("--output-dir", type=Path, default=PROJECT_ROOT / "output" / "q3") + parser.add_argument("--device", choices=("auto", "cpu", "cuda"), default="auto") + parser.add_argument("--explanation-batch-size", type=int, default=128) + parser.add_argument("--validation-batch-size", type=int, default=128) + parser.add_argument("--skip-validation", action="store_true", help="Skip the labeled official validation split.") + parser.add_argument("--no-frames", action="store_true", help="Do not extract approximate candidate frames from source videos.") + parser.add_argument("--resume", action="store_true", help="Rerun into an existing partial output directory, overwriting this run's outputs.") + args = parser.parse_args() + run(args) + + +if __name__ == "__main__": + main() diff --git a/submit/requirements.txt b/submit/requirements.txt new file mode 100644 index 0000000..f4c5616 --- /dev/null +++ b/submit/requirements.txt @@ -0,0 +1,6 @@ +numpy>=1.26,<3 +scipy>=1.12 +scikit-learn>=1.4 +torch>=2.2 +transformers>=4.44 +matplotlib>=3.8