Spatial-BEATs 文件级实现清单
1. 文档目的
这份文档把当前确定的 Spatial-BEATs 简化版方案,收紧成可以直接指导编码的文件级实现清单。
目标是回答四件事:
- 哪些现有文件直接复用
- 哪些新文件需要实现
- 每个模块的职责是什么
- 从
FOA到LLM spatial tokens的张量形状如何变化
这份清单默认遵循当前主线:
- 重点训练
FOA -> BEATs trunk的空间表征 - 后面的模块只承担
readout / supervision - 不以复杂
slot decoder为主线 - 最终给
LLM的 token 是固定2.5 Hz 10 s输入默认输出25个 spatial tokens
2. 默认配置
- 输入采样率:
16 kHz - 输入通道:
4个FOA通道,分别为W / X / Y / Z - 预处理输出通道:
7 - 空间特征:
W_logmel / X_logmel / Y_logmel / Z_logmel / IVx / IVy / IVz mel bins:128- Qwen-like mel 参数:
sample_rate=16000num_mel_bins=128n_fft=400win_length=400hop_length=160dither=0.0
- source vocabulary:
/apdcephfs_cq12/share_302080740/user/schmittzhu/data/fsd50k/FSD50K.ground_truth/final_vocabulary.csv - source vocabulary fields:
label_id / final_label - source class count:
65 - 主干初始化:
BEATs_iter3+ AS2M pre-trained patch embed dim:512encoder dim:768encoder layers:12- 最终时间 token rate:
2.5 Hz - 最大同时源数:
4 - 当前监督:弱时间窗口 + 固定位置
- batch 时间组织:每个样本自己的
T_s_i = round(duration_i * 2.5),batch 内 pad 到T_s_max
3. 推荐文件布局
建议保留原始 BEATs 文件不动,新实现集中放在新增文件里:
- 复用:BEATs.py
- 复用:backbone.py
- 新增:
spatial_beats.py - 新增:
spatial_modules.py - 新增:
spatial_loss.py - 新增:
spatial_dataset.py - 新增:
train_spatial_beats.py
推荐原则:
- 原始
BEATs.py作为参考和权重来源,不直接改 - 原始
backbone.py直接复用,不改 Transformer 内部 - 新的
Spatial-BEATs在新文件里组装
4. 现有文件如何复用
4.1 BEATs.py
当前 BEATs 里最重要的可复用部分是:
BEATsConfigpost_extract_projlayer_normTransformerEncoder的使用方式- 预训练 checkpoint 的参数命名
当前不直接复用的部分是:
preprocess()patch_embedding = Conv2d(1, embed_dim, ...)predictor
原因:
preprocess()只支持单通道fbankpatch_embedding只支持1通道输入predictor是 AudioSet 分类头,不适合当前 spatial encoder
4.2 backbone.py
这里建议直接复用:
TransformerEncoderTransformerSentenceEncoderLayer- attention / FFN / pos_conv / layer_norm
当前 Spatial-BEATs 不需要改这些内部结构。
5. 新文件实现清单
5.1 spatial_beats.py
这个文件放主模型类,负责把所有模块串起来。
建议包含:
SpatialBEATsConfigSpatialBEATsload_beats_pretrained()
5.1.1 SpatialBEATsConfig
职责:
- 在
BEATsConfig基础上补充 spatial encoder 需要的超参
建议新增字段:
foa_feature_channels = 7sample_rate = 16000num_mel_bins = 128target_token_rate = 2.5source_vocab_pathsource_label_id_field = "label_id"source_label_name_field = "final_label"source_num_classes = 65max_sources = 4readout_layers = 1 or 2slot_hidden_dim = 768distance_head_type = "regression"llm_hidden_dim
5.1.2 SpatialBEATs.__init__
职责:
- 初始化所有子模块
- 建立 trunk 与 heads
- 提供 BEATs checkpoint 兼容结构
建议持有的子模块:
preprocessor: SpatialBEATsPreprocessorpatch_embedding: SpatialPatchEmbeddinglayer_normpost_extract_projdropout_inputencoder: TransformerEncoderfrequency_pool: FrequencyPooltemporal_resampler: TemporalResamplertemporal_readout: ShallowTemporalReadoutslot_readout: FixedSlotReadoutprediction_heads: SpatialPredictionHeadsprojector: SpatialTokenProjector
5.1.3 SpatialBEATs.forward()
建议拆成几个清晰阶段:
preprocessorpatch embeddingBEATs trunkfrequency poolingtemporal resamplingtemporal readoutslot readout + headsprojector
建议输出一个结构化对象或 dict,至少包含:
{
"foa_feat": foa_feat,
"patch_tokens": patch_tokens,
"encoder_memory": encoder_memory,
"temporal_tokens": temporal_tokens,
"spatial_embeddings": spatial_embeddings,
"slot_latents": slot_latents,
"pred_activity": pred_activity,
"pred_azi_logits": pred_azi_logits,
"pred_ele_logits": pred_ele_logits,
"pred_dist": pred_dist,
"pred_class_logits": pred_class_logits,
"llm_spatial_tokens": llm_spatial_tokens,
"temporal_padding_mask": temporal_padding_mask,
"target_num_steps": target_num_steps,
}
这样训练和调试都更方便。
5.2 spatial_modules.py
这个文件放所有新增模块。
建议包含以下类。
5.2.1 SpatialBEATsPreprocessor
职责:
- 把
FOA waveform变成7通道空间特征图
输入:
waveform: [B, 4, T]
默认 10 秒示例:
waveform: [B, 4, 160000]
内部步骤:
- 对
W/X/Y/Z做STFT - 计算四个通道的
log-mel - 计算
IVx / IVy / IVz - 将
IV映射到mel维 - 按通道拼接
输出:
foa_feat: [B, 7, T_f, 128]
默认示例:
- 如果
frame_shift = 10 ms - 对
10 s,T_f约为1000 - 因此输出近似:
foa_feat: [B, 7, 1000, 128]
作用:
- 用显式
FOA物理特征替代原始BEATs的单通道fbank
5.2.2 SpatialPatchEmbedding
职责:
- 把
7通道空间特征图变成 patch tokens
建议实现:
Conv2d(7, 512, kernel_size=patch, stride=patch, bias=cfg.conv_bias)
输入:
foa_feat: [B, 7, T_f, 128]
输出中间张量:
patch_grid: [B, 512, T_p, F_p]
flatten 后输出:
patch_tokens: [B, N_p, 512]
其中:
T_p = floor((T_f - p_t) / s_t) + 1F_p = floor((128 - p_f) / s_f) + 1N_p = T_p * F_p
如果第一版保持和当前 BEATs 一样的等步长 patch,且 patch=(16,16):
T_f ≈ 1000 -> T_p ≈ 62128 -> F_p = 8N_p ≈ 496
则一个典型输出是:
patch_tokens: [B, 496, 512]
作用:
- 用多通道 spatial features 替换原始单通道 patch stem
5.2.3 FrequencyPool
职责:
- 把 trunk 的二维 patch grid 沿频率维压缩成时间序列
输入:
encoder_memory: [B, N_p, 768]grid_size = (T_p, F_p)
先 reshape:
grid_memory: [B, T_p, F_p, 768]
再沿 F_p 做 pooling:
temporal_patch_tokens: [B, T_p, 768]
推荐第一版:
mean pooling over F_p
作用:
- 把 patch 级二维结构转成时间序列
5.2.4 TemporalResampler
职责:
- 把 patch 级时间步压到固定
2.5 Hz
输入:
temporal_patch_tokens: [B, T_p, 768]
输出:
temporal_tokens: [B, T_s_max, 768]temporal_padding_mask: [B, T_s_max]
其中:
- 每个样本:
T_s_i = round(duration_i * 2.5)
- batch 内:
T_s_max = max_i T_s_i
对 10 s:
T_s_i = 25
因此单个 10 s 样本的有效长度是:
25
推荐实现:
- 第一版:线性插值
- 备选:轻量
Conv1d下采样
作用:
- 明确固定最终时间基准
- 保证最终给 LLM 的 token rate 就是
2.5 Hz
5.2.5 ShallowTemporalReadout
职责:
- 把 trunk 输出的时间序列再整理一遍
- 让它更适合作为空间 embedding
建议实现:
1~2层轻量TransformerEncoder- 或最小版
LayerNorm + MLP
输入:
temporal_tokens: [B, T_s_max, 768]
输出:
spatial_embeddings: [B, T_s_max, 768]
作用:
- 这就是当前主线里最重要的 LLM 前空间 embedding
5.2.6 FixedSlotReadout
职责:
- 给 encoder-only 训练提供多源监督出口
- 不引入复杂 decoder
输入:
spatial_embeddings: [B, T_s_max, 768]
建议最小实现:
Linear(768, 4 * 768)- reshape 成:
slot_latents: [B, T_s_max, 4, 768]
备选更稳实现:
Linear(768, 768) -> GELU -> Linear(768, 4 * 768)- reshape 后:
slot_latents: [B, T_s_max, 4, 768]
作用:
- 每个时间步提供
4个固定 source 槽位 - 用于承载多源监督
注意:
- 这里的
4只是 supervision 容量 - 不影响最终输出给 LLM 的 token 数
5.2.7 SpatialPredictionHeads
职责:
- 从
slot_latents上读出显式空间监督
输入:
slot_latents: [B, T_s_max, 4, 768]
输出:
pred_activity: [B, T_s_max, 4]pred_azi_logits: [B, T_s_max, 4, 360]pred_ele_logits: [B, T_s_max, 4, 180]pred_dist: [B, T_s_max, 4, 1]pred_class_logits: [B, T_s_max, 4, C_cls]
作用:
pred_activity:弱时间窗口下的源存在性pred_azi_logits:方位角分类pred_ele_logits:仰角分类pred_dist:连续距离回归pred_class_logits:辅助类别监督
当前默认建议:
C_cls = 65- labels 来自
final_vocabulary.csv
5.2.8 SpatialTokenProjector
职责:
- 把
spatial_embeddings投影到 LLM hidden size
输入:
spatial_embeddings: [B, T_s_max, 768]
输出:
llm_spatial_tokens: [B, T_s_max, d_llm]
推荐实现:
Linear(768, D_mid)GELULayerNormLinear(D_mid, d_llm)
注意:
- encoder-only 第一阶段可以不训练这个模块
- 但接口最好先保留
5.3 spatial_loss.py
这个文件放 target 组织、matching 和 loss 计算。
建议包含:
build_time_window_mask()match_fixed_slots()compute_spatial_losses()
5.3.1 build_time_window_mask()
输入:
- source start / end
- clip duration
- 每个样本的
T_s_i - batch 的
T_s_max
输出:
window_mask: [B, num_gt, T_s_max]
作用:
- 把弱时间标注映射到
2.5 Hz时间轴
5.3.2 match_fixed_slots()
输入:
pred_activity: [B, T_s_max, 4]pred_azi_logits: [B, T_s_max, 4, 360]pred_ele_logits: [B, T_s_max, 4, 180]pred_dist: [B, T_s_max, 4, 1]pred_class_logits: [B, T_s_max, 4, C_cls]gt targets
输出:
- 每个时间步的 slot 分配结果
推荐第一版:
per-step fixed-slot matching
作用:
- 不依赖复杂 decoder,也能对 4 个固定槽位施加多源监督
5.3.3 compute_spatial_losses()
建议输出各项 loss:
L_activityL_aziL_eleL_distL_cls_auxL_temp
总损失:
L_total =
lambda_act * L_activity
+ lambda_azi * L_azi
+ lambda_ele * L_ele
+ lambda_dist * L_dist
+ lambda_cls * L_cls_aux
+ lambda_temp * L_temp
各项作用:
L_activity:窗口外负样本,窗口内弱正样本L_azi/L_ele:方向监督L_dist:距离连续回归L_cls_aux:帮助保持 source-level 语义区分L_temp:固定位置场景下的时间平滑
5.4 spatial_dataset.py
这个文件负责把数据组织成模型和 loss 可直接使用的格式。
建议一个 batch sample 至少包含:
waveform: [4, T]source_labelssource_azimuthsource_elevationsource_distancesource_start_timesource_end_timeclip_durationsource label id / nameshould map throughfinal_vocabulary.csv
建议 collate 后的 batch 字段:
waveform: [B, 4, T]padding_mask: [B, T]或Nonegt_sources: 变长列表,或 pad 成固定max_gtclip_duration_seconds: [B]
建议在 dataset 或 collate 阶段完成:
- 时间窗口离散化到每个样本自己的
T_s_i,再 pad 到T_s_max - 距离归一化
- 角度离散化成 bin index
- label id/name 映射到统一 vocabulary index
5.5 train_spatial_beats.py
这个文件负责训练阶段组织。
建议职责:
- 构建 model
- 加载 BEATs checkpoint
- 配置参数组和学习率
- 调用 dataset / loss
- 执行 encoder-only 训练
建议阶段:
Stage 1
- 训练:
preprocessorpatch_embeddingtemporal_resamplertemporal_readoutslot_readoutprediction_heads
projector默认不训练- trunk 可以:
- 先只解冻高层
- 或全量解冻但用更小 lr
Stage 2
- 接上
projector - 再和 LLM 接口联合训练
6. 预训练权重加载清单
建议在 spatial_beats.py 里实现 load_beats_pretrained()
6.1 直接加载的参数
从 BEATs checkpoint 里直接复用:
layer_norm.*post_extract_proj.*encoder.pos_conv.*encoder.layers.*encoder.layer_norm.*
6.2 部分继承或重新初始化的参数
patch_embedding
原始 BEATs:
Conv2d(1, 512, ...)
新的 Spatial-BEATs:
Conv2d(7, 512, ...)
推荐初始化:
W_logmel通道拷贝原始卷积权重- 其他
6个通道初始化为:- 零
- 或小随机值
这样可以最大程度保留原始单通道初始化稳定性。
新模块随机初始化
以下模块默认新初始化:
SpatialBEATsPreprocessorFrequencyPoolTemporalResamplerShallowTemporalReadoutFixedSlotReadoutSpatialPredictionHeadsSpatialTokenProjector
7. 10 秒输入的完整张量流
下面给一个默认示例。
假设:
- batch size 为
B - 输入时长
10 s - 采样率
16 kHz T = 160000mel bins = 128- patch 为
16 x 16
7.1 从波形到 trunk
输入波形
[B, 4, 160000]
预处理后
[B, 7, 1000, 128]
patch conv 后
[B, 512, 62, 8]
flatten + transpose 后
[B, 496, 512]
layer_norm[B, 496, 512]
post_extract_proj[B, 496, 768]
TransformerEncoder[B, 496, 768]
7.2 从 trunk 到 spatial embeddings
reshape 回 patch grid
[B, 62, 8, 768]
frequency pooling
[B, 62, 768]
temporal resample 到
2.5 Hz- 单个
10 s样本有效长度为25 - mixed-length batch 中张量形状为
[B, T_s_max, 768]
- 单个
shallow temporal readout
[B, T_s_max, 768]
这一步得到:
spatial_embeddings: [B, T_s_max, 768]
7.3 从 spatial embeddings 到训练 heads
fixed-slot expand
[B, T_s_max, 4, 768]
activity head
[B, T_s_max, 4]
azimuth head
[B, T_s_max, 4, 360]
elevation head
[B, T_s_max, 4, 180]
distance head
[B, T_s_max, 4, 1]
class auxiliary head
[B, T_s_max, 4, C_cls]
7.4 从 spatial embeddings 到 LLM 接口
- spatial projector
[B, T_s_max, d_llm]
这就是最终给 LLM 的 spatial tokens。
关键点:
- 单个
10 s样本:10 s -> 25个有效 token - mixed-length batch:张量按
T_s_maxpadding - 不是
25 * 4 4只存在于监督头里
8. 推荐实现顺序
建议按下面顺序写代码。
spatial_modules.py- 先实现
preprocessor / patch embedding / frequency pool / temporal resampler / readout / heads / projector
- 先实现
spatial_beats.py- 把主链路串起来
- 先跑通 forward 和 shape
spatial_dataset.py- 先把数据组织成
2.5 Hz时间轴上的 supervision,并支持 mixed-length batch padding
- 先把数据组织成
spatial_loss.py- 先实现
L_activity / L_azi / L_ele / L_dist / L_cls_aux - 最后再补
L_temp
- 先实现
train_spatial_beats.py- 最后接训练循环
9. 第一版验收标准
在不接 LLM 的前提下,第一版至少应满足:
- forward 全链路 shape 正确
- 能成功加载 BEATs trunk 预训练参数
10 s输入稳定输出:- 对单个
10 s样本有25个有效 token - batch 张量输出为
spatial_embeddings [B, T_s_max, 768] - batch 张量输出为
llm_spatial_tokens [B, T_s_max, d_llm]
- 对单个
- 训练阶段能算出:
L_activityL_aziL_eleL_distL_cls_aux
- loss 能稳定回传到 trunk
10. 一句话总结
当前最合理的文件级实现方式是:
- 不改原始
BEATs主体文件 - 用新文件组装一个
FOA -> spatial features -> BEATs trunk -> 2.5Hz spatial embeddings -> fixed-slot supervision heads -> projector的Spatial-BEATs - 训练时真正要训好的是前面的
spatial_embeddings - 后面的 heads 只是为了把空间监督显式接出来