197 lines
12 KiB
Markdown
197 lines
12 KiB
Markdown
# 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 配置](https://huggingface.co/Qwen/Qwen2.5-Omni-3B/blob/main/config.json)、
|
||
[Transformers 4.52.3 编码器 API](https://github.com/huggingface/transformers/blob/v4.52.3/src/transformers/models/qwen2_5_omni/modeling_qwen2_5_omni.py)、
|
||
[4.57.6 编码器 API](https://github.com/huggingface/transformers/blob/v4.57.6/src/transformers/models/qwen2_5_omni/modeling_qwen2_5_omni.py)。
|
||
脚本加载 Thinker checkpoint 后释放语言解码器,仅将冻结的视觉/音频塔移到 GPU;没有 Thinker forward。
|
||
|
||
局部提取已适配 **Transformers 4.52.3 和 4.57.6**,现有 L20 / ms-swift 容器若是 4.57.6,
|
||
无需降级,也不要为这一步重新安装整个 `requirements.txt`。旧脚本只接受精确的 4.52.3,
|
||
因此即使接口存在也会在加载模型前主动报错;更新脚本后重跑原 smoke 命令即可。
|
||
两个版本都校验原生 token 输出形状与权重完整性,并显式使用 `use_fast=False` 固定图像预处理路径。
|
||
未知版本仍在媒体解码前报错;`--preview-only` 不受编码器版本检查影响。
|
||
该兼容范围只针对本局部提取脚本,不代表 Thinker KV replay 或 ROMA 的全部接口可互换。
|
||
|
||
输出的 `config.transformers` 记录实际运行版本,不再写死 4.52.3。
|
||
同一局部特征实验的 train/test sessions 应统一版本、权重和提取配置;加载器会拒绝混用版本。
|
||
接口兼容不等于不同版本的浮点特征逐位相同,已有全局 Omni 特征无需重提。
|
||
|
||
## 1. EasyCom:先检查框,再跑一个 4s smoke test
|
||
|
||
在已有项目依赖的 GPU 机器运行,以下路径沿用之前实验;raw 数据根按实际挂载修改。
|
||
不需要先重新编码 2FPS 视频,直接读取原 `Main/Video_Compressed`。
|
||
|
||
```bash
|
||
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 encoded
|
||
)
|
||
|
||
# 不加载模型;输出四张带框图片,请先检查框是否真在人脸上。
|
||
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 像素;这份 EasyCom 标注的框坐标包含 banner。
|
||
EasyCom 默认使用 `encoded`,先减去当前源视频的裁剪起点,再裁框:
|
||
`x_camera = x_annotation - (source_width - 1920)`。例如已核对的 Session_4 原视频宽度
|
||
为 2124,应减去 204;不同视频可能不同,不能统一硬编码。y 也按实际裁剪起点转换。
|
||
`camera` 仅用于已经手动换算到裁后图像的标注;Ego4D 无此 banner 裁剪,默认仍为 `camera`。
|
||
之前示例中的 `camera` 参数会导致这份原始 EasyCom 标注统一右偏,现已更正。
|
||
若当前 shell 的 `LOCAL_ARGS` 还含旧参数,请修改它,或将 `--box-coordinate-space encoded`
|
||
放在 `"${LOCAL_ARGS[@]}"` **后面**覆盖旧值。仅更新代码不会覆盖显式传入的旧参数。
|
||
预览会打印 source/cropped 尺寸与实际扣除偏移,同时在预览目录生成 `boxes.json`,
|
||
记录原框、裁后框与源帧号。必须检查框确实落在人脸上,不能只看最终特征维度。
|
||
官方帧号与 banner 说明见 [EasyCom 数据说明](https://github.com/facebookresearch/EasyComDataset#notes-and-known-issues)。
|
||
|
||
若已经用错误坐标提取局部视觉特征,必须重新从图像提取,不能靠修改 `.pt` 中的框坐标修复。
|
||
推荐输出到新目录(例如 `local_qwen_1s_encoded`),并把训练的 `LOCAL_ROOT` 指向新目录。
|
||
原全局 Omni 特征和 baseline 无需重算;旧局部 V/AV 训练结果不可与修正后的特征混用。
|
||
|
||
```bash
|
||
python - <<'PY'
|
||
import torch
|
||
p = torch.load('features/local_smoke/Session_1.pt', map_location='cpu', weights_only=False)
|
||
print('partial:', p['partial'])
|
||
print('transformers:', p['config']['transformers'])
|
||
print('box coordinates:', p['config']['box_coordinate_space'])
|
||
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 和本地 checkpoint 加载测试,
|
||
在 4.52.3 / 4.57.6 上均适用;checkpoint 加载测试需要 `accelerate`。
|
||
可先检查本机编码器接口,再跑完整回归:
|
||
|
||
```bash
|
||
python -m unittest -v test_local_turn_features.TinyQwenEncoderTests
|
||
python -m unittest -v test_easycom_head_objectives test_local_turn_features
|
||
```
|
||
|
||
## 2. 提取全部 12 sessions(只做一次)
|
||
|
||
```bash
|
||
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 进度;模型只加载一次。
|
||
已有文件默认会报错;恢复时可只列尚未完成的 sessions,或增加 `--reuse-existing`
|
||
逐文件验证完整性、时间对齐和提取配置后跳过已完成文件。
|
||
`--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) |
|
||
|
||
```bash
|
||
# 先做 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`。
|
||
|
||
手动训练只需在原命令增加:
|
||
|
||
```bash
|
||
--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 接口
|
||
|
||
完整命令见 [Ego4D 四组局部增强实验](ego4d_local_baselines.md):新增 manifest 批量提取、
|
||
续跑校验、`run_ego4d_local_baselines.py` 四组运行入口和匹配 seed 的增量汇总。
|
||
|
||
提取脚本同样支持 `--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 无提升,不宜直接断言多人视觉无用。
|