SOND 说话人分离的数据预处理:FunASR 从音频波形到 PSE 标签的完整链路

SOND 说话人分离的数据预处理:FunASR 从音频波形到 PSE 标签的完整链路 SOND 说话人分离的数据预处理FunASR 从音频波形到 PSE 标签的完整链路【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASRFunASR 是开源语音识别工具包内置的 SOND 模型专攻多人重叠说话场景下的说话人分离。想跑通或微调它绕不开数据预处理这条链路音频要洗成统一波形逐帧标注要变成模型认得的 PSE 标签。全局概览一条样本闯过的五道关你大概见过这类场景会议室里两个人同时开口标注文件里同一时刻要打上两个说话人的标记。这给预处理带来三个和普通 ASR 不同的麻烦特征和标签的时间轴对不齐——梅尔频谱按 10ms 一帧标注却精确到采样点重叠帧一个窗口里同时属于多个说话人简单的 0/1 标签装不下模型输入里除了音频还有说话人嵌入向量这类向量最容易过拟合。整个链路可以拆成下面这张图左边是特征侧右边是标签侧最后在编码器入口汇合图下方一句话SOND 的预处理本质是特征帧率对齐 重叠标签压缩两条支线在编码器前汇合。上面这张总览图里SOND 复用的正是 frontend 与 specaug 两个可插拔组件预处理逻辑并不在模型文件里独苗实现。输入侧先把波形洗成同一规格不同录音设备给出来的波形幅度尺度和采样率都不统一不先洗一遍后面所有特征都不可比。这里有个反直觉的细节重采样并不在前端里做WavFrontend直接假设输入已经是16kHz重采样通常在读取音频的 dataset 层完成。前端真正动手的是两件事把浮点波形乘32768115还原到 16bit 整数量级再按 Kaldi 的 fbank 约定抽特征。对应的代码就在WavFrontend.forward中waveform input[i][:waveform_length] if self.upsacle_samples: waveform waveform * (1 15) # 还原到 16bit 整数量级 mat kaldi.fbank( waveform, num_mel_binsself.n_mels, # 默认 80 维梅尔特征 frame_lengthmin(self.frame_length, waveform_length / self.fs * 1000), frame_shiftself.frame_shift, ditherself.dither, # 注入极小噪声抹平浮点量化痕迹 window_typeself.window, # 默认 hamming 窗 sample_frequencyself.fs, )这里的kaldi.fbank是仓库内纯 PyTorch 复现的 Kaldi 兼容实现不依赖外部二进制。前端的关键参数集中在__init__签名里建议直接记住默认值参数默认值作用fs16000前端假设的采样率上游必须保证n_mels80梅尔维数决定特征宽度frame_length / frame_shift25ms / 10ms帧长与帧移约 100 帧/秒windowhamming加窗函数dither1.0幅度扰动系数抑制量化痕迹upsacle_samplesTrue波形 ×32768lfr_m / lfr_n1 / 1LFR 帧拼接默认关闭cmvn_fileNone指定后加载统计量做 CMVN前端实现的完整入口在 funasr/frontends/wav_frontend.py同一文件里还有WavFrontendOnline供流式场景复用参数约定完全一致。特征侧fbank 之后还有两笔修正梅尔频谱算出来不等于可以直接喂模型。SOND 的链路里还保留了两处修正分别治两个不同的毛病。LFR 帧拼接与 CMVN 各修什么LFRLow Frame Rate低帧率拼接的思路是每lfr_n帧取一组把相邻lfr_m帧拼成一行。相当于用更粗的时间步换取更宽的上下文对重叠语音这种状态变化慢的信号很划算默认lfr_m1, lfr_n1时不生效。CMVNCepstral Mean and Variance Normalization倒谱均值方差归一化则治设备偏差它用训练集预先统计好的每维均值和方差把每条音频的特征分布拉到同一水平。注意它和逐句 MVN不同——统计量来自全局文件推理时不会漂移这也是它在工业链路里更稳的原因。标签侧逐帧标注如何变成 PSE 类别说话人分离的标注是逐样本的 0/1 序列[0,0,1,1,1,1,1,0]表示第 1 个说话人在第 3~6 个采样点说话。这种分辨率和 100 帧/秒的特征对不上直接训练模型只会把边界帧学成一团噪声。上图就是 SOND 要处理的典型场景多位说话人交替甚至同时开口标签侧的工作就是把这种谁在什么时候说的逐点信息压缩成和特征同帧率的帧级标签。对齐靠LabelAggregate完成用512 样本窗口、128 样本步长32ms/8ms滑窗窗内过半样本为 1 就判该帧在说本质是一次多数投票# as_strided 零拷贝滑窗切帧 output input.as_strided((bs, nframe, self.win_length, label_dim), strides) # 窗内过半样本为 1 才判为说话中 output torch.gt(output.sum(dim2), self.win_length // 2).float()它的文档注释特意说明默认参数与torch.stft的加窗、填充行为保持一致这样标签和特征的帧偏移天然对齐不用你在外面再手写一遍 padding。三行代码看懂 PSE 幂集编码帧级标签还有最后一道坎重叠帧同时属于多个说话人单标签分类装不下。SOND 用 PSEPower-Set Encoding幂集编码解决——每个说话人组合就是一个独立类别。DiarSondModel.forward里的核心只有三行pad_bin_labels F.pad(binary_labels, (0, self.max_spk_num - binary_labels.shape[2]), constant, 0.0) raw_pse_labels torch.sum(pad_bin_labels * self.power_weight, dim2, keepdimTrue) # 0/1 向量按 2 的幂加权求和 pse_labels torch.argmax((raw_pse_labels.int() self.int_token_arr).float(), dim2) # 与预存整数表比对查表得类别 idmax_spk_num默认16意味着词表是 2^16 65536个组合无人在说对应 0 号类别。加权和再查表的写法比逐样本位运算快这也是它被选为默认实现的原因。模型主流程与损失组装都在 funasr/models/sond/e2e_diar_sond.py聚合逻辑在 funasr/models/sond/label_aggregation.py改窗口参数时两处要一起看。增强与在线洗牌防过拟合的三件小事说话人嵌入向量是 SOND 最娇气的输入训练集里几个人模型就容易只认得这几个人。仓库用三种轻量手段打散这种过拟合触发时机和扰动对象各不相同手段扰动对象生效时机目的SpecAug梅尔特征的时间/频率维度训练模式模拟特征盲区提升时间鲁棒性ProfileAug说话人嵌入加噪或混入其他说话人训练模式防止依赖特定嵌入向量onfly shuffle说话人标签的排列顺序每次 forward打破某 id 固定属于某人的位置偏置dither波形幅度前端提取时抹平低位量化痕迹其中 ProfileAug 是 SOND 专属支持给嵌入加高斯噪声和混入另一说话人的嵌入mixup两种模式实现见 funasr/models/specaug/profileaug.py。onfly shuffle 则在每次前向随机置换说话人顺序代价极低建议训练时保持开启。 效果验证用 DER 给预处理打分预处理做没做好不用等训练结束才有反馈DiarSondModel.forward每个 step 都会返回一组诊断指标最核心的就是 DERDiarization Error Rate说话人分离错误率指标含义der(漏检 误报 混淆) / 有效语音帧sad_mr / sad_fr语音级漏检率 / 误报率mi / fa / cf说话人级漏检 / 误报 / 混淆两个容易踩的坑值得提前说明。其一若编码器带下采样pred与标签长度可能差几帧代码用length_diff_tolerance 2强制截齐超出这个容差说明帧率对齐出问题了其二如果 cf混淆项居高不下先别调编码器——多数情况是标签聚合窗口与特征帧移没对齐或者max_spk_num小于标注里实际出现的说话人数组合类别被截断后模型只能瞎猜。 落地建议一句话总结SOND 的数据预处理 波形统一 80 维梅尔频谱 滑窗多数投票 幂集编码四条流水线在编码器入口对齐帧率后汇合。三条可直接落地的建议改窗口参数时成对检查LabelAggregate的 512/128 与 fbank 的 25ms/10ms 必须自洽否则 cf 项会先于任何模型问题报警。训练开启 dither 与 ProfileAug推理前确认model.eval()已调用——ProfileAug 只在self.training下生效切换不干净会出现训练/推理分布不一致。说话人数可能超过 16 时先调大max_spk_num再谈其他词表不够时PSE 标签被 pad 截断所有下游指标都会失真。【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考