Files
icassp2027-experiment/docs/ego4d_local_baselines.md
T

349 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ego4D:近期音频/逐人视觉增强的四组对照
本页四组均包含全局 Thinker 输入 G。若要完全不输入 Thinker 状态,使用
[encoder-only A / V / AV 对照](ego4d_encoder_only.md),不要把本页的 audio/visual/av 当作纯编码器组。
目标是检验 EasyCom 上的局部特征收益能否迁移到 Ego4D,而不是预先承诺提分。
保留现有 Ego4D 全局 Omni 特征 G、EgoSpeak manifest 和 target;不重提 Thinker,
不重新编码视频,不重划 train/test。新增的两路侧路特征一次提取、三组复用:
| 实验名 | 输入 | 回答的问题 |
|---|---|---|
| baseline | G | 同训练配置下的基准 |
| audio | G + A | 最近 1s 音频是否补回全局 readout 丢失的信息? |
| visual | G + V | 人物局部视觉是否有效? |
| av | G + A + V | 在音频增强之外,视觉是否仍有增益?重点比较 AV − A |
`audio` **不是纯音频模型**;四组都包含 G。A 是同一 clip 的视频音轨,
16kHz mono 后按 `[max(0,t-1),t)` 切窗,每 200ms 用冻结 Qwen audio encoder 重编码。
V 在原视频的固定 2FPS 网格读取可见人物框,冻结 Qwen vision encoder 后依次做
空间、逐人、最近 1s 有效帧的均值池化。Qwen 3B 的 A/V 均保存原生 2048D;
训练时各投影到 256D,融合后沿用现有预测 head。无有效框时 V=0 且 validity=False,
不能拿缺框当损坏样本直接删掉,否则四组测试样本不一致。
默认统一 **4 epochs、无 dev、LSTR-MCE、无 transition auxiliary**;batch=512、
lr=7e-5、weight_decay=5e-5、warmup=1.6 epochs、warmup_factor=0.3、dropout=0.2。
训练只取 manifest 的 train,评测只取 test。现有正式 manifest 应显示 346 train / 87 test;
程序读取该文件,不会自行随机拆分。预检 test 只检查输入完整性,不计算指标或选模型。
## 1. 设置远程路径
在 `/data/wangx/Code/icassp2027-experiment` 的 Bash 中执行。只需把 `FEATURE_ROOT`
改成**实际已提完的 Ego4D 全局特征目录**;不能误用 EasyCom 的 Session 文件。
建议本轮固定已有 8s 全局特征,四组不要混用不同 context、层数或 checkpoint。
```bash
cd /data/wangx/Code/icassp2027-experiment
export CUDA_VISIBLE_DEVICES=0
export OMP_NUM_THREADS=4 MKL_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4
DATA_ROOT=/data/wangx/Datasets/ego4d
MODEL=/data/wangx/Models/Qwen2.5-Omni-3B
FEATURE_ROOT=/替换为实际的/Ego4D全局特征目录
TARGET_ROOT="${DATA_ROOT}/ego4d_target_frame"
MANIFEST="${DATA_ROOT}/ego4d_egospeak.tsv"
LOCAL_ROOT="${DATA_ROOT}/omni_features/local_qwen_1s"
LOCAL_ARGS=(
--dataset ego4d --data-root "$DATA_ROOT" --manifest "$MANIFEST"
--feature-root "$FEATURE_ROOT" --target-root "$TARGET_ROOT"
--model-id "$MODEL" --device cuda:0 --dtype bfloat16 --flash-attention-2
--video-fps 2 --visual-window-seconds 1 --audio-window-seconds 1
)
```
原始输入应存在:`v1/clips/<uid>.mp4`、`v1/annotations/av_train.json` 和 `av_val.json`。
全局特征既支持 `<feature-root>/<uid>.pt`,也直接支持原批量提取器的
`<feature-root>/train/<uid>.pt`、`<feature-root>/test/<uid>.pt`;无需复制或建立软链接。
提取、训练、推理使用同一个路径解析器;同一 UID 在这几个位置存在多份文件会报错,避免误读旧特征。
这里 `av_val.json` 是原 AVD 标注的文件名,不意味着把它直接当作本实验的 test 划分;
train/test 始终按 EgoSpeak manifest。非标准导出路径可加 `--ego4d-annotations <path1> <path2>`。
局部编码器支持现有 Transformers 4.57.6,也支持 4.52.3;同一次实验所有 clips 应统一版本。
## 2. 先检查一条,再做 4s smoke
```bash
# 检查文件、全局时间网格和 target 长度;不加载模型,不解码,不写输出。
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
--limit 1 --dry-run --output-root "$LOCAL_ROOT"
# 不加载模型,写带框预览;确认框落在人脸上、clip-local 帧号正确。
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
--limit 1 --max-steps 20 --preview-only \
--output-root features/ego4d_local_preview
# 同时提取 V 和 A;无需另跑音频脚本。
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
--limit 1 --max-steps 20 \
--output-root features/ego4d_local_smoke
```
预览位于 `features/ego4d_local_preview/<uid>_preview/`,包含图片与 `boxes.json`。
最好再用 `--split test --limit 1` 检查一条 test clip;`--dry-run` 不能替代像素与框检查。
4s smoke 应得到 V/A 各 `[20,2048]`、`partial=True`;此目录不能用于正式训练。
V-valid 不必是 100%,Ego4D 有无可见人物和缺框的时刻;若有明显人物却长期无有效框,先停下来检查标注。
### 前 4s 没有人物:增加预览范围和数量
旧预览固定最多 4 张;`--max-steps 20` 又把范围限制到前 4s。
现在可指定数量和时间范围,并仅从有有效人物框的网格帧中均匀抽样:
```bash
# 前三条 clip:在整条 clip 中各取最多 40 张有效带框图片。
# 不要再带 --max-steps 20;不传 preview-end 就使用完整可用时间范围。
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
--limit 3 --preview-only --preview-count 40 --preview-with-boxes \
--output-root features/ego4d_local_preview_people
# 也可聚焦第一条 clip 的 30–120s;实际不足 120s 时截到 clip 结束。
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
--limit 1 --preview-only --preview-count 60 \
--preview-start 30 --preview-end 120 --preview-with-boxes \
--output-root features/ego4d_local_preview_30_120s
```
每张图标注时间、源帧号和有效人数;`preview.json` 记录候选帧数、有效帧数、实际输出数,
`boxes.json` 记录当前这次输出的图片和框。无有效框时明确报告 0 张,可换片段或核对帧号对齐,
不会伪造框或强行当作成功。`--preview-with-boxes` 只筛选预览,**不删除特征提取或评测中的无框帧**。
默认仍最多 4 张。改变范围时建议换输出目录;旧 JPG 不会自动删除,以当前 JSON 清单为准。
## 3. 提取完整 manifest:前台、模型只加载一次
```bash
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
--reuse-existing --output-root "$LOCAL_ROOT"
```
前台显示 clip 编号、视觉 frame 进度和音频 state 进度。中断后可重跑同一命令:
只复用完整且样本、时间、特征维度、版本及提取配置一致的 `.pt`;部分或不兼容文件会报错。
输出原子替换,失败的临时文件不会被当作成功样本。不要用 `--overwrite` 当作“继续”。
checkpoint 的 ID 是用户声明,不是权重文件校验和:不要在同一个 ID/路径下替换权重再复用旧特征。
可用 `--split train|test`、`--start-index N --end-index M` 或 `--limit N` 选择子集。
索引是**先按 split 过滤,再按 manifest 顺序的 1-based 闭区间**;不传 split 时保留整个文件的行顺序。
如果自行在多卡不同终端运行,必须选择互不重叠的区间和同一套编码器配置;默认不启动后台任务。
### L20 机器:8 卡并行提取,不使用软链接
下面使用已经检查过框的原始视频,以及原 8s 全局特征目录。不要再传旧的占位符 `LOCAL_ARGS`,
不要带 `--preview-only`、`--preview-with-boxes` 或 `--max-steps`。新入口会自动添加校验后续跑。
```bash
cd /data1/yfzx/wangx/Code/icassp2027-experiment
MULTIGPU_ARGS=(
--gpus 0 1 2 3 4 5 6 7
--cpu-threads 4
--data-root /data1/yfzx/wangx/Datasets/ego4d
--manifest /data1/yfzx/wangx/Datasets/ego4d/ego4d_egospeak.tsv
--feature-root /data1/yfzx/wangx/Datasets/ego4d/omni_features/open_stream_2fps_8s_decord
--target-root /data1/yfzx/wangx/Datasets/ego4d/ego4d_target_frame
--output-root /data1/yfzx/wangx/Datasets/ego4d/omni_features/local_qwen_1s
--model-id /data1/yfzx/wangx/Models/Qwen2.5-Omni-3B
--dtype bfloat16 --flash-attention-2
--video-fps 2 --visual-window-seconds 1 --audio-window-seconds 1
--decoder-backend decord --decoder-threads 1
--log-root logs/ego4d_local_8gpu
)
# 只检查路径和分片,不加载 Qwen、启动 GPU 任务或写日志。
python extract_ego4d_local_multigpu.py "${MULTIGPU_ARGS[@]}" --dry-run
# 确认分片和路径后,前台管理 8 个独立 worker。
python extract_ego4d_local_multigpu.py "${MULTIGPU_ARGS[@]}"
```
默认 433 clips 分配为 55/54/54/54/54/54/54/54,按 manifest 顺序轮转,每个 UID 只属于一个 worker。
每个 worker 用 `CUDA_VISIBLE_DEVICES=<指定卡号>` 隔离到一张卡,内部 `--device cuda:0`;
会覆盖父 shell 遗留的 GPU mask。GPU 编号必须是当前容器暴露的卡,而不是另一个容器的编号。
这不是 DDP;每张卡独立处理 clips,不需要 GPU 间同步。全量数据默认每个 worker 至多加载一次编码器。
如使用 `--limit`,少于 GPU 数量时只启动有任务的卡。
主进程打印每卡状态,详细的逐帧/逐 state 进度位于启动时打印的
`logs/ego4d_local_8gpu/<时间_PID>/gpu0.log` 至 `gpu7.log`。每次运行另建日志目录,不覆盖旧日志;
同目录的 `launch_plan.json` 保存每卡 UID 和命令。可在另一个终端查看指定卡:
```bash
tail -f logs/ego4d_local_8gpu/<本次时间_PID>/gpu0.log
```
不用 `nohup` 或 shell 的 `&`,主命令会一直等待;Ctrl+C 或一个 worker 失败时,会停止本次启动的其他
worker 及其进程组,不杀无关训练。已完成的 `.pt` 保留,重跑同一命令会校验后跳过;
失败时先读对应日志。不要同时另开同输出根的重复提取任务。
正常进度仍使用简短摘要;worker 失败时改为输出日志最后 64 KiB 中的最后一段 traceback
(没有 traceback 则输出末尾 40 行),不再只留下最后 300 个字符。原始日志仍完整保留。
最终输出是实体文件 `local_qwen_1s/<uid>.pt`,每个文件同时含 A/V,不需要第二个音频任务。
无框帧仍保留在 5Hz 特征/标签网格中,不能按预览筛选删除。
8 workers 也会增加 CPU、主机 RAM、磁盘 I/O 和标注 JSON 的解析开销;不要预设能达到 8 倍速度。
主机内存不足时改为 `--gpus 0 1 2 3` 并重跑,完成的文件仍可复用;不同次分片也不会改写已完成特征。
#### 已核实的末尾 bbox 记录
2026-08-28 在 `31488ff6-05ef-4362-9786-35981b06f307` 上核对到:
原视频 8999 帧、30 FPS,有效索引为 `0..8998`;标注从 0 开始,
`clip_end_frame=8999`、`clip_end_sec=299.9666666666666`,唯一越界索引是 `8999`。
这是无对应解码帧的末尾记录,不应把该框移到 `8998`,也不应将所有框减 1。
随后在 `ac7d3d16-d74d-4fe3-a1e0-cab0b1adf492` 核对到:
原视频 8998 帧、30 FPS,有效索引为 `0..8997`;`clip_end_frame=8998`、
`clip_end_sec=299.9333333333333`,但 bbox 仍到 `8999`,越界索引为 `[8998, 8999]`。
因此只兼容一个末尾索引还不够;这第二例同样是标注结束位置与媒体长度一致、bbox 尾部多出记录。
提取器默认只在以下条件同时满足时忽略末尾记录:所有越界索引都属于 `N、N+1`,
`N=视频帧数=clip_end_frame`,标注帧起点为 0、时间起点在 1 微秒内为 0,
且标注时长与 `N / source_fps` 相差不超过 0.1 毫秒。
容差限制的是**末尾索引位置**,不是框数:即使仅一条记录,出现在 `N+2` 或更远处也不接受。
负索引、缺少确认边界、超过显式容差的越界或时长不匹配仍会报错,并打印具体范围;不自动跳过 clip。
这是基于已核实样例的受限兼容规则,不代表所有 Ego4D 越界记录都可忽略。
日志显示忽略的末尾索引和框数;新 `.pt` 的 `box_frame_audit` 与预览目录的
`box_frame_audit.json` 保存原始范围、视频帧数、标注边界和忽略记录。
不修改原视频、JSON、target 或已完成的特征。首例已核对的 1499 行 target 仍对应 1499 行特征,截止时间为
`0.2..299.8s`,2 FPS 的最后一个输入采样时刻仍为 `299.5s`。
该处理不改编码器/池化配置,之前完成且通过校验的特征可以继续复用;同步修复后重跑原 8 卡命令即可。
#### 更长但边界一致的尾部:显式授权,不修改默认值
用户随后报告 `d2cb4045-8ef1-4288-8398-ab6d4d6a0709`:视频为 8987 帧、30 FPS,
`clip_end_frame=8987`、`clip_end_sec=299.5666666666666`,但 bbox 延伸到 8999。
两种长度信息一致,越界的 8987–8999 共 13 个索引都没有对应的视频帧。
这解释了默认两帧规则为什么失败;不是 GPU OOM,也不意味着应平移全部框。
本地没有该远程视频,本次用用户提供的边界建立 CPU 回归 fixture,未重新解码实际文件。
新增参数 `--ego4d-max-terminal-overrun-frames K`,只授权 `[N, N+K)` 内的尾部索引:
- 默认 `K=2` 不变;`K=0` 禁用所有尾部兼容;负值被拒绝。
- 仍必须满足零起点、`N=source_frames=clip_end_frame` 和同样的时长一致性校验。
- 上限按最远索引计算,不按框数或越界索引数量计算;本例 `K=13` 不接受索引 9000 或更远处。
- 原始视频、标注 JSON 和 target 不改动;仅从本次内存 bbox 映射排除无对应视频帧的尾部。
- 媒体仍需覆盖原始 target/全局特征要求的时长。参数不能修复截断的视频、补帧、缩短 target 或绕过全局/局部时间线校验。
- 参数及实际尾部长度保存在每个新文件的 `box_frame_audit`,不改变编码器配置/旧特征复用条件。
同步代码后,使用上面已经配置好的 `MULTIGPU_ARGS` 续跑:
```bash
python extract_ego4d_local_multigpu.py "${MULTIGPU_ARGS[@]}" \
--ego4d-max-terminal-overrun-frames 13
```
若在新终端中运行,先重新定义上一节的完整 `MULTIGPU_ARGS` 数组。无需软链接,不使用 `--overwrite`。
当前失败会停止本次启动的其他 workers;已原子保存且复用检查通过的 `.pt` 不会重提,
中途被停止、尚未保存的 clip 会重新计算。不要对更大的越界直接设无限容差,应先核对新日志。
新生成的该 clip 可以检查审计和网格长度:
```bash
python - <<'PY'
from pathlib import Path
import numpy as np
import torch
root = Path('/data1/yfzx/wangx/Datasets/ego4d')
clip_id = 'd2cb4045-8ef1-4288-8398-ab6d4d6a0709'
p = torch.load(root / 'omni_features/local_qwen_1s' / f'{clip_id}.pt',
map_location='cpu', weights_only=False)
target = np.load(root / 'ego4d_target_frame' / f'{clip_id}.npy', mmap_mode='r')
assert len(p['visual']) == len(p['audio']) == len(p['times']) == len(target)
assert not p['partial']
audit = p['box_frame_audit']
assert audit['source_frames'] == audit['declared_bounds']['clip_end_frame'] == 8987
assert audit['ignored_frame_indices'] == list(range(8987, 9000))
print('visual:', tuple(p['visual'].shape), 'audio:', tuple(p['audio'].shape))
print('target:', target.shape, 'last cutoff:', float(p['times'][-1]))
print('bbox audit:', audit)
PY
```
提取完成后,原训练入口也直接用同一个原始全局特征根,无需 flat 索引:
```bash
CUDA_VISIBLE_DEVICES=0 OMP_NUM_THREADS=4 MKL_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 \
python run_ego4d_local_baselines.py \
--data-root /data1/yfzx/wangx/Datasets/ego4d \
--feature-root /data1/yfzx/wangx/Datasets/ego4d/omni_features/open_stream_2fps_8s_decord \
--local-feature-root /data1/yfzx/wangx/Datasets/ego4d/omni_features/local_qwen_1s \
--seeds 0 --dry-run
```
## 4. 跑 seed0 四组
```bash
EXP_ARGS=(
--data-root "$DATA_ROOT" --manifest "$MANIFEST"
--feature-root "$FEATURE_ROOT" --target-root "$TARGET_ROOT"
--local-feature-root "$LOCAL_ROOT"
--checkpoint-root checkpoints/ego4d_local_b1
--result-root results/ego4d_local_b1
)
# 逐 clip 校验全部 train/test 输入、局部配置,打印命令但不训练。
python run_ego4d_local_baselines.py "${EXP_ARGS[@]}" --seeds 0 --dry-run
# 前台顺序训练/评测 baseline、audio、visual、av,并自动汇总。
python run_ego4d_local_baselines.py "${EXP_ARGS[@]}" --seeds 0
# 补同设置 seed1/2,不覆盖 seed0。
python run_ego4d_local_baselines.py "${EXP_ARGS[@]}" --seeds 1 2
```
自动检测 G:`[T,L,D]` 使用 concat-MLP,`[T,D]` 使用原单层 head;不要求为 last4 重提全局特征。
如果原 stacked baseline 使用 fixed-mean,可在四组统一加 `--layer-fusion fixed-mean`。
不要拿不同 epoch/layer-fusion 的旧 baseline 直接计算新增分支的增量。
Ego4D target 由 EgoSpeak/LSTR 的 `MCE` 当作 binary/multi-positive 权重直接读取;
因此代码对 `dataset=ego4d` 保留其原始二值行,不强行套用 EasyCom 的四状态约束,
也绝不把 `[0,0,0]` 等行擅自改成 `[1,0,0]` background。预检会汇总并打印所有
非 canonical pattern,便于核对数据。EasyCom 仍严格限制为四个 activity states。
当前四组实验关闭 transition objective,因此不会从非 canonical 行伪造 transition
标签;如果以后启用 transition auxiliary,需要先定义语义和显式 valid mask。
预检不会一次把 433 条全局特征全部装进 RAM;实际训练仍沿用原训练器,会加载整个 train split。
last4 特征可能需要数十 GB 主机内存。多卡并行 head 训练也会复制主机内存,不是只看 GPU 是否空闲。
若只跑一组,增加 `--variants audio`;若某一组训练中断,默认拒绝覆盖现有 checkpoint/result。
可先运行剩余组合,例如 `--variants visual av --seeds 0`,或为重跑选择新的输出根。
baseline 单独运行不要求局部特征;整个四组 family 会在任何训练前检查所有必要局部特征。
## 5. 打印结果
```bash
python summarize_local_baselines.py \
--root results/ego4d_local_b1 --dataset ego4d \
--protocol causal_valid_only --paired-deltas
# 对齐 EgoSpeak 的 full-timeline 表格时,单独报告这个协议。
python summarize_local_baselines.py \
--root results/ego4d_local_b1 --dataset ego4d \
--protocol egospeak_full_timeline --paired-deltas
```
表格给出每个 seed、均值与 sample std,以及匹配 seed 的 `A−G`、`V−G`、`AV−G`、`AV−A`。
不要混用两个评测协议。优先看 10-horizon mean mAP、wearer AP 和 AV−A,
不要只凭单 seed 的 0.2s mAP 宣称跨数据集提升。
## 论文可解释的范围
这轮回答“在冻结的全局 Omni readout 上加入近期编码器特征是否有用”,不是 Qwen 对 wav2vec
的受控替换实验,也不是逐人音视频对齐模型。使用提供的人物 tracks,忽略 wearer 框、讲话区间和
说话人标签;缺框不向未来插值。但提供框本身不是在线检测,不能据此声称端到端在线视觉系统。
新增分支带来额外参数,若收益稳定,再补参数量匹配的全局 head 控制。
本轮不改变已有的音频重采样、标签索引和 horizon 定义。5Hz 是输出网格;
不等于视觉获得 5FPS 唇动信息,也不等于已完成原始波形层面的端到端因果审计。
如后续修正物理时间协议,必须在所有对照组统一修正并重新跑,不能只改增强组。
## 回归测试
```bash
python -m unittest -q test_easycom_head_objectives test_local_turn_features test_ego4d_local_workflow test_ego4d_local_multigpu test_ego4d_box_frames
```
CPU 回归包括 manifest 顺序/防交叉重复、完整批量提取的模型复用、断点文件校验、
单层/多层全局特征、测试集输入预检,以及合成 Ego4D 上四组各 4 epochs 的训练/推理与增量汇总。
另测试原 train/test 目录、433 clips 的 8 卡完整互斥分片、GPU mask、独立日志、失败/中断时清理自有进程。
末尾框回归覆盖一帧/两帧越界、严格边界验证、较远稀疏越界拒绝、1499 行完整提取、
审计保存和旧特征续跑;合成测试中,处理后的特征/时间戳/掩码/人物记录与先去掉越界框的结果完全相同。
另覆盖启动期和运行期的长异常信息保留,正常进度仍保持简短。
真实 Ego4D 像素、提供框和 3B/L20 提取仍需按上面的 preview/smoke 步骤验证。