片段语音唤醒

片段语音唤醒

实现方式

  1. 输入长度为T帧的语音片段,输出1个类别向量(每个类是一个唤醒词),超过阈值则唤醒;✔
    1. 如果只有一个命令词(唤醒任务),可以只输出一个概率值;
    2. 适用模型:感受野较大的模型(CNN)(因为唤醒词持续数帧)、RNN
  2. 输入长度为T帧的语音片段,输出T帧类别向量(每个类是一个唤醒词);计算唤醒词类别的平滑后验概率等后处理方式,超过阈值则唤醒;
    1. 适用模型:与上下文有关的RNN
  3. 输入长度为T帧的语音片段,输出T帧类别向量(每个类是是唤醒词的子词、音节、音素),后处理唤醒词组成单元的概率,超过阈值则唤醒;✔
    1. 适用模型:无限制(一帧语音特征可表示一个组成单元)
  4. 输入长度为T帧的语音片段,输出T帧类别向量(每个类是是唤醒词的子词、音节、音素、以及非唤醒词),WFST解码,搜索最优路径,最优路径是否出现唤醒词则唤醒;✔
    1. 适用模型:HMM模型
  5. 输入长度为T帧的语音片段,输出N个类别向量(每个类是是唤醒词或子词、字符),搜索最优路径,最优路径是否出现唤醒词则唤醒;
    1. 适用模型:transformer模型、RNN-T模型、CTC模型

[✔表示已做过实验]

基于CNN的片段语音唤醒

模型

  • CNN结构、depthwise卷积、pointwise卷积、dialted卷积;
  • 感受野1.84s,无padding(输入N帧,输出N-184帧);
  • 残差结构,不同感受野的特征层element-wise add;

模型图

02

模型对比

  • 对比论文“Small-Footprint Keyword Spotting with Multi-Scale Temporal Convolution”的模型结构,上述模型结果更好;

训练、推理特征输入方式

  1. offline cmvn

    ​ 1.1 语音特征文件做好offline cmvn后

    ​ 1.1.1 训练时封装batch时pad_sequence补零,batch内最大长度不足1.84s的也补零至1.84s,训练时不再做cmvn;

    ​ 1.1.2 训练时封装batch时pad_sequence复制第一帧,batch内最大长度不足1.84s的也复制第一帧至1.84s,训练时不再做cmvn;✔

    ​ (前期做了cmvn,训练中不用做cmvn,训练速度快)

    ​ 1.2 语音特征文件没做cmvn

    ​ 1.2.1 训练时封装batch时pad_sequence补零,batch内最大长度不足1.84s的也补零至1.84s

    ​ 1.2.1.1 训练时根据实际长度做offline cmvn;(与1.1.1特征相同)

    ​ 1.2.1.2 训练时根据pad_sequence后长度做offline cmvn;

    ​ 1.2.2 训练时封装batch时pad_sequence复制第一帧,batch内最大长度不足1.84s的也复制第一帧至1.84s,训练时再做offline cmvn;

    ​ 1.2.2.1 训练时根据实际长度做offline cmvn;(与1.1.2特征相同)

    ​ 1.2.2.2 训练时根据pad_sequence后长度做offline cmvn;

  2. sliding cmvn,将上述offline cmvn换成sliding cmvn

  3. global cmvn,将上述offline cmvn换成global cmvn

    1. 语音特征文件没做cmvn,利用训练集统计出的全局均值方差,训练时封装batch时pad_sequence补零,再做cmvn后,pad_sequence部分值不为0,作用于网络中,但它们没有物理含义,espnet和wenet框架都是这样做的,会有问题吗?

训练目标函数

  1. max_pooling loss 只取最大概率帧

    1. target = filler时:$loss=\min\limits_T(1-P_{keyword})$ (min pooling)

      target = keyword时:$loss=\max\limits_TP_{keyword}$ (max pooling)

    2. 取正样本某帧的最大正类概率值,让这帧概率越大越好,取负样本某帧的最小负类概率值,让这帧的概率越大越好;

  2. more_label loss 逐输出分类(每个输出都有标签)(普通的逐帧分类)

    1. 扩充正负样本标签数量,取正样本输出时间轴所有帧,让所有时间帧的正类概率越大越好,取负样本输出时间轴所有帧,让所有时间帧的负类概率越小越好;
    2. 由于输入输出不等长,标签序列未知,要求训练数据片段内尽可能是一个分类(比如输入2s,输出16帧,16帧都要是正样本,因此2s尽可能是唤醒词,并且前后静音短)
  3. mean_pooling loss 平均概率

    1. 取输出实际片段内平均概率,求bceloss

实验

最好结果:more_label loss训练得到初始模型,再用max_pooling loss训练;

输出概率层

  • 单唤醒词,一个分类,用sigmoid

数据

训练集

  • train_p522h_n4000h:筛选出满足唤醒词对齐区间在2.5s内正样本数据,负样本segment为2s语音。正样本101万条,负样本717万条。

训练数据数据增广

  • 正样本:原始“小源小源”音频14万条,加噪、速度扰动、扩充为102万条(667小时),筛选出满足唤醒词对齐区间在2.5s内正样本数据,得到101万条,522小时;
  • 负样本:原始4000小时负样本、300小时mobvoihotwords,加噪900小时(5类噪声、NoiseX_92_carcafenoise、两两混合制造重叠说话人的效果),切割为2s音频,舍弃小于2s的,的搭配717万条,4000小时;

验证集

  • 训练的几个初始模型经过测试集,从测试集中选取far高、recall低作为cv;
  • 正样本:0.6-6.5s,4k条,3h;
  • 负样本:1.1-3.1s,15k条,11h;

推理过程

  • 窗长200帧,窗移30帧的滑动窗口语音特征送入网路进行前向计算,输出16帧内,若同时满足最大概率值高于阈值1和平均概率高于阈值2,则唤醒;
    • 送入整句语音,实际长度小于1.85s,复制第一帧直到总长度为1.85s,计算概率(输出一帧);实际长度在1.85s-2s之间,按实际长度计算概率,不用滑动;实际长度大于2s,进行滑动
  • FLOPS = 2M(未复用)、模型参数33k

实验结果

  • tdnnf[2]:chain_tdnnf8_432_48_l27r27_id_merge_all_sub4_12000_basedTDNN_smaller_phone_lm_more_epoch/graph_add_two2;
  • tdnn[3]:chain_tdnn5_256_l39r27_id_merge_all_sub4_12000_basedTDNN_smaller_phone_lm_multitask/graph;
  • tdnn10:keyword_lib/data/asr_config_xiaoyuan/tdnn_10/xyxyxy_2gram;
  • n10:exp_1/mdtc_offline_cmvn_p382h_n4000h_max_pooling/6.pt;

C++ api

  • 模型pt格式转为onnx格式,用onnxruntime的动态链接库进行调用;

  • 每次读入包大小(0.32s/0.3s)的语音片段,

    • 到达最后一个包并且累计语音片段长度小于1.85s,复制第一帧直到总长度为1.85s,计算概率(输出一帧);
    • 到达最后一个包并且累计语音片段长度在1.85s-2s之间,按实际长度计算概率,不用滑动;
    • 未到达最后一个包并且累计语音片段长度大于2s,计算概率,若唤醒,则向后滑动1s,1s内不检测,若未唤醒,则向滑动0.3s,继续判断;
  • RTF在原来1/3~1/2;