Files
icassp2027-experiment/docs/ego4d_local_baselines.md
T

342 lines
19 KiB
Markdown
Raw Normal View History

2026-08-28 00:25:54 +08:00
# Ego4D:近期音频/逐人视觉增强的四组对照
2026-08-28 09:36:35 +08:00
本页四组均包含全局 Thinker 输入 G。若要完全不输入 Thinker 状态,使用
[encoder-only A / V / AV 对照](ego4d_encoder_only.md),不要把本页的 audio/visual/av 当作纯编码器组。
2026-08-28 00:25:54 +08:00
目标是检验 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 在这几个位置存在多份文件会报错,避免误读旧特征。
2026-08-28 00:25:54 +08:00
这里 `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 有无可见人物和缺框的时刻;若有明显人物却长期无有效框,先停下来检查标注。
2026-08-28 00:46:05 +08:00
### 前 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 清单为准。
2026-08-28 00:25:54 +08:00
## 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 尾部多出记录。
2026-08-28 12:17:02 +08:00
提取器默认只在以下条件同时满足时忽略末尾记录:所有越界索引都属于 `N、N+1`,
`N=视频帧数=clip_end_frame`,标注帧起点为 0、时间起点在 1 微秒内为 0,
且标注时长与 `N / source_fps` 相差不超过 0.1 毫秒。
容差限制的是**末尾索引位置**,不是框数:即使仅一条记录,出现在 `N+2` 或更远处也不接受。
2026-08-28 12:17:02 +08:00
负索引、缺少确认边界、超过显式容差的越界或时长不匹配仍会报错,并打印具体范围;不自动跳过 clip。
这是基于已核实样例的受限兼容规则,不代表所有 Ego4D 越界记录都可忽略。
日志显示忽略的末尾索引和框数;新 `.pt` 的 `box_frame_audit` 与预览目录的
`box_frame_audit.json` 保存原始范围、视频帧数、标注边界和忽略记录。
不修改原视频、JSON、target 或已完成的特征。首例已核对的 1499 行 target 仍对应 1499 行特征,截止时间为
`0.2..299.8s`,2 FPS 的最后一个输入采样时刻仍为 `299.5s`。
该处理不改编码器/池化配置,之前完成且通过校验的特征可以继续复用;同步修复后重跑原 8 卡命令即可。
2026-08-28 12:17:02 +08:00
#### 更长但边界一致的尾部:显式授权,不修改默认值
用户随后报告 `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
```
2026-08-28 00:25:54 +08:00
## 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 直接计算新增分支的增量。
预检不会一次把 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
2026-08-28 00:25:54 +08:00
```
CPU 回归包括 manifest 顺序/防交叉重复、完整批量提取的模型复用、断点文件校验、
单层/多层全局特征、测试集输入预检,以及合成 Ego4D 上四组各 4 epochs 的训练/推理与增量汇总。
另测试原 train/test 目录、433 clips 的 8 卡完整互斥分片、GPU mask、独立日志、失败/中断时清理自有进程。
末尾框回归覆盖一帧/两帧越界、严格边界验证、较远稀疏越界拒绝、1499 行完整提取、
审计保存和旧特征续跑;合成测试中,处理后的特征/时间戳/掩码/人物记录与先去掉越界框的结果完全相同。
另覆盖启动期和运行期的长异常信息保留,正常进度仍保持简短。
2026-08-28 00:25:54 +08:00
真实 Ego4D 像素、提供框和 3B/L20 提取仍需按上面的 preview/smoke 步骤验证。