Files
icassp2027-experiment/docs/local_feature_baseline.md
T

9.0 KiB
Raw Blame History

B1:局部视觉+近期混合音频,普通池化融合

这是用于判断“原 Omni readout 是否丢掉有用的局部 AV 线索”的基线, 不是已证明的改进,也不是 multi-speaker attention 模型。 保留原 [T,4,2048] 特征;不重新提取全局 Thinker,不改标签和 AP 协议。

固定实验定义

分支 冻结特征 更新/池化 训练时处理
原全局特征 Thinker last4,[T,4,2048] 原来的 8s memory、5Hz 原 concat-MLP,8192→2048
局部视觉 V 每个人脸裁剪,Qwen vision encoder + merger,2048D 原视频固定 2FPS;先空间 mean,再逐人等权 mean,再最近 1s 有效帧等权 mean 可训练 LN→Linear(2048,256)→GELU
近期音频 A Qwen audio encoder + 原生 output projection,2048D 每 200ms 重编码 [max(0,t-1),t) 混合音频;仅池化有效输出 tokens 同上

Qwen 3B 的 2048 是编码器原生输出维度,256 只是新增 head 的可选投影宽度。 特征文件永远保存原始维度,--local-projection-dim 0 可不降维。 正常 AV 组合为 [2048 global,256 V,256 A,2 validity],共 2562D, 接 LN→Linear(2562,1024)→GELU→Dropout→Linear(1024,30),输出仍为 [10,3]。 单模态只附加对应一路与一个有效标志;不传 --local-feature-root 时保留原 head 参数结构。

局部视觉没有运动编码、ASD 分数、speaker-aware attention、身份 embedding 或图网络。 参与者 ID 仅保存供审计,不输入 head。人数多的帧不会因 token 多而得到更大权重。 没有有效框时视觉为零、valid=False;音频静音仍 valid=True。 缺少音轨、损坏的媒体或缺少应有的标注文件会报错,不会悄悄冒充“空输入”。

时间约定是 [t-1,t):例如 t=1.0s,视觉取 0.0、0.5s,排除 1.0s 的帧。 音频先截断再做 mel 和编码,不对完整视频的音频编码后切片。 这保证相对于 readout 时刻的输入因果性,但不保证编码器在窗口内部逐 token 因果。

实现依据:Qwen 3B 配置、 Transformers 4.52.3 编码器 API。 脚本加载 Thinker checkpoint 后释放语言解码器,仅将冻结的视觉/音频塔移到 GPU;没有 Thinker forward。

1. EasyCom:先检查框,再跑一个 4s smoke test

在已有项目依赖的 GPU 机器运行,以下路径沿用之前实验;raw 数据根按实际挂载修改。 不需要先重新编码 2FPS 视频,直接读取原 Main/Video_Compressed。

export CUDA_VISIBLE_DEVICES=0
MODEL=/data1/yfzx/wangx/Models/Qwen2.5-Omni-3B
DATA_ROOT=/data1/yfzx/wangx/Datasets/easycom/extracted_easycom
export FEATURE_ROOT=/data1/yfzx/wangx/Datasets/easycom/omni_features/open_stream_2fps_8s_last4_stack
export TARGET_ROOT=/data1/yfzx/wangx/Datasets/easycom/easycom_target_frame
export LOCAL_ROOT=/data1/yfzx/wangx/Datasets/easycom/omni_features/local_qwen_1s

LOCAL_ARGS=(
  --dataset easycom --data-root "$DATA_ROOT"
  --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
  --box-kind face --box-coordinate-space camera
)

# 不加载模型;输出四张带框图片,请先检查框是否真在人脸上。
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
  --samples 1 --max-steps 20 --preview-only --output-root features/local_preview

# 4s / 20 states;此文件明确标记 partial,不能误拿去训练整段。
python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
  --samples 1 --max-steps 20 --output-root features/local_smoke

EasyCom face 标注首帧是 1,head 首帧是 0;按原始解码帧号取框。 视频裁去左侧身份 banner,保留右侧 1920 像素;默认框坐标属于裁后的相机图像。 如果你的导出文件的框坐标含 banner,需要改成 --box-coordinate-space encoded。 必须通过 preview 检查这个约定,不能只看最终特征维度。 官方帧号与 banner 说明见 EasyCom 数据说明。

python - <<'PY'
import torch
p = torch.load('features/local_smoke/Session_1.pt', map_location='cpu', weights_only=False)
print('partial:', p['partial'])
for key in ('visual', 'audio'):
    x = p[key]
    print(key, tuple(x.shape), x.dtype, 'finite:', bool(torch.isfinite(x).all()),
          'valid:', p[key + '_valid'].float().mean().item())
print('person features:', tuple(p['persons']['features'].shape))
print('times:', p['times'][0].item(), p['times'][-1].item())
PY

3B 预期 V/A 各 [20,2048],时间 0.2–4.0s;没有人脸的时刻允许 V 无效。 本机已验证 CPU 对齐/融合/合成数据训练推理,真实 checkpoint + GPU 提取仍需此 smoke test。 另有不下载权重的小型随机 Qwen 编码器 API 测试。可在已有依赖的环境运行:

python -m unittest -v test_easycom_head_objectives test_local_turn_features

2. 提取全部 12 sessions(只做一次)

python extract_turn_local_features.py "${LOCAL_ARGS[@]}" \
  --samples 1 2 3 4 5 6 7 8 9 10 11 12 --output-root "$LOCAL_ROOT"

默认前台顺序执行,显示视觉帧进度和音频 state 进度;模型只加载一次。 已有文件会报错,恢复时在 --samples 中只列尚未完成的 sessions。 --overwrite 是明确重算,不会重用不兼容文件。不要覆盖原始 feature root。 persons 还保留每帧变长的逐人特征、frame_offsets、框、帧号和时间,供后续 B2 复用。 若在不同挂载路径提取同一 checkpoint,使用相同 --encoder-id;不同权重不可共用这个 ID。

3. 四组对照:4 epochs、无 dev、相同种子和 loss

名称 输入
baseline 原 Omni last4
audio 原 Omni + 近期音频
visual 原 Omni + 局部视觉
av 原 Omni + 两路局部特征(B1)
# 先做 seed0 四组;默认前台跑完训练后立即评测 Session 3/2/1。
CUDA_VISIBLE_DEVICES=0 bash run_easycom_local_baselines.sh all 0

# 再补 seed1/2;不会覆盖 seed0。
CUDA_VISIBLE_DEVICES=0 bash run_easycom_local_baselines.sh all 1 2

# 也可把互不重复的组合分配到不同卡,不要复用相同 variant/seed 输出路径。
# CUDA_VISIBLE_DEVICES=1 bash run_easycom_local_baselines.sh audio 0

python summarize_local_baselines.py --protocol causal_valid_only
python summarize_local_baselines.py --protocol egospeak_full_timeline

脚本使用训练 Session 4–12、epoch=4、batch=512、lr=7e-5、wd=5e-5、warmup=1.6 epochs、 warmup_factor=0.3、dropout=0.2、LSTR-MCE、不加 transition objective。 FEATURE_ROOT/TARGET_ROOT/LOCAL_ROOT 用上面 export 的值,也可自行覆盖。 结果分别在 checkpoints/easycom_local_b1 和 results/easycom_local_b1。 如要不降维,设置 LOCAL_PROJECTION_DIM=0,并换 CHECKPOINT_ROOT/RESULT_ROOT。

手动训练只需在原命令增加:

--local-feature-root "$LOCAL_ROOT" --local-modalities av --local-projection-dim 256

手动推理增加 --local-feature-root "$LOCAL_ROOT";模态组合从 checkpoint 读取。 Loader 会检查 sample、时间、label_indices、维度、valid mask 和 encoder/pooling 配置。 训练与评测必须用同一种 AP 协议作比较;不要混用 full-timeline 与 causal-valid 的表格。

Ego4D 接口

提取脚本同样支持 --dataset ego4d:输入根为 /data/wangx/Datasets/ego4d, 读取 v1/clips/<uid>.mp4、v1/annotations/av_train.json 与 av_val.json, --samples 传已有 Omni .pt 的 clip UID。--target-root 指向 ego4d_target_frame。 可用 --ego4d-annotations 指定其他导出路径。先用 --preview-only --max-steps 20 检查框。 读取的是与 clip_uid 对应的可见人物 tracks;不使用 wearer 框、voice_segments、讲话标签, 不对缺失帧用未来框插值。载入大 JSON 需要相应主机内存。 train/infer 使用原 --dataset ego4d --manifest ... 加上述 local 参数; EasyCom 的运行 shell 只管理 EasyCom,不能用于 Ego4D 的 split。

如何解读实验

先看每种子 paired 的 0.2s wearer AP 与 10-horizon mean mAP,不能提前承诺提升。 若只有 A 有效,先支持“近期声音补充”的解释;V/AV 的额外收益才支持局部视觉线索有价值。 这几组 head 参数量不同,结果不是纯架构公平性证明;下一步应做同参数量全局头/打乱局部输入控制。

这一版使用提供的框(部分身份标注经人工修正),不是在线 detector 的端到端测试。 论文必须披露这个协议,后续补在线检测框实验;不得把 close-mic、讲话标签或测试集调优引入输入。 提供框读取即使只取当前帧,也不代表框生成器本身是在线因果的。 2FPS 静态脸特征不能代表完整唇动建模;若 V 无提升,不宜直接断言多人视觉无用。