前面的文章(语音降噪论文“A Hybrid Approach for Speech Enhancement Using MoG Model and Neural Network Phoneme Classifier”的研读 )梳理了论文的思想。本篇就开始对其实践,主要分以下几步:1,基于一个语料库算出每个音素的单高斯模型;2,训练一个输出是一帧是每个音素概率的NN分类判别模型;3,算法实现及调优。
1,得到每个音素的单高斯模型
要想得到每个音素的单高斯模型,首先得做好音素对齐,知道某一帧对应哪个音素。论文里用的是英文的TIMIT语料库,已经做好了音素对齐,知道了哪个时间段是哪个音素。现在做的是中文的语音降噪,最好能有一个像TIMIT一样已经做好音素对齐的语料库。网上搜了 一下,没有已做好音素对齐的中文语料库,只能自己做音素对齐了。做语音识别时用过清华的Thchs30语料库,且它在中文语音识别领域里知名度较高,于是就决定基于Thchs30做每个音素的单高斯模型。自己来做音素对齐,肯定花不少时间,最好是有开源工具帮忙来做。幸运的是找到了支持中文音素对齐的工具speech-aligner(https://github.com/open-speech/speech-aligner),它是基于kaidi的,我用过一段时间kaldi,自然对它用起来也比较顺手。speech-aligner里音素较多,主要是因为音素带音调了(包括轻声共5个音调,分别是0/1/2/3/4,0表示轻声)。Phoneme.txt里是音素和id的映射,wav.scp放wav文件名对应的音频位置,text里放wav文件对应的中文和带调拼音,自己弄时依葫芦画瓢就可以了。比较好的是Thchs30语料库里已有了每个wav文件对应的中文和带调拼音(trn文件里),我只需要写python脚本把它们集中起来使用,这让我省了不少时间,尤其是在拼音标注上。用speech-aligner做好音素对齐后得到的是out.ali文件,里面指出了哪个时间段是什么音素,示意如下图:
从上图可以看出,0~25ms是音素y,25~460ms是音素e_3。
接下来就是基于out.ali得到每帧是哪个音素。这里的帧跟语音识别里的帧一样,帧长25ms和帧移10ms。依旧用python脚本处理后得到csv文件,每行代表一个wav文件的信息,开头是文件名,后面就是每帧对应的音素id,示意如下图:
有了每帧跟音素的映射后就开始算每个音素的单音素高斯模型了。统计出语料库中每个音素共有多少帧,每帧求出对数幅度谱。由于帧长是25ms,16k HZ采样下是400个点,所以STFT用512个点的,即有512个频段。时域数据转换到频域后是复数形式,除了0和N/2维外其他值是对称的,所以只要(N/2 + 1)表示频域数据就可以了。当N=512时,N/2 + 1是257,从而对数幅度谱的数据是257维的。对每维分别算均值和方差,这样每个音素的单高斯模型就有了,它是257维的,每维有均值和方差。
2,分类判别NN模型训练
原理篇中说过这是个典型的分类问题,需要训练一个分类模型 。在做KWS(keyword spotting,关键词识别)时我们就训练过分类模型,这里可以拿来用,不过要做一些修改,比如分类label的处理上。我把语料库按8:1:1分成训练集/验证集/测试集,网络用的是CNN(卷积神经网络)。论文是几年前的啦,当时神经网络刚流行,更多用的是DNN全连接网络。训练时发现模型不能收敛,在测试集下准确率跟论文里的差不多。我认真想了下,不能收敛也是合理的,毕竟音素对齐时就不是很准确,尤其是两个音素的临界处。论文里用9帧(当前帧+前后各4帧)作为网络的输入。我试过单帧(当前帧)输入 / 3帧(当前帧+前后各1帧)输入 /5帧(当前帧+前后各2帧)输入 / 7帧(当前帧+前后各3帧)输入 / 9帧(当前帧+前后各4帧)输入,准确率都差不多,考虑到更多帧的输入导致模型参数更多,我最后选了单帧输入。既然准确率跟论文里差不多,我也就没再深究。这样基于NN的分类判别模型就训练好了。
3,算法实现和调优
上面的两个模型做好后就开始做算法实现了。依旧在python下实现,因为python里有好多现成的库,实现起来更快,要把更多的时间放在降噪效果调优上。算法实现依据原理篇中给出的步骤一帧一帧的做。先根据前25帧(约250ms)算出噪声单高斯模型的均值和方差的初始值用于后面迭代。在每一帧里,先根据NN模型求出这帧对应每个音素的后验概率,然后算对数谱,根据数学表达式算出降噪后的每一维的对数谱,再做反变换得到时域的PCM值。最后再更新噪声的单高斯模型的均值和方差,用于下一帧的计算。算法实现还是挺快的,有两个值α和β需要tuning。先简单调了下这两个参数,能起到部分降噪效果,接下来就是调优了。
经过好多次的尝试,得到了一个相对不错的α和β值,在各种不同的SNR下MOS分平均能提高0.3左右,与论文里说的差不多(由于语言/语料库/网络模型等不一样,结果与论文里的也不可能完全一样)。我也将其与webRTC里的ANS(用的是我手头上的一个C语言的版本,且有3个不同的level,分别是弱/一般/激进 )的降噪效果做了比较,具体MOS分如下表:
训练分类模型时都是干净语音,降噪时都是拿带噪语音去算每帧是每个音素的后验概率,准确率相对干净语音会低一些,进而会影响到降噪效果。如果拿降噪后的语音去再次算后验概率,准确率会高些,降噪效果也会好些。于是对于一段降噪的语音,我把算法做了几次迭代,即拿前一次降噪后的语音经过NN算后验概率,再次降噪,直到最后一次的降噪结果作为输出。实践下来第一次迭代后MOS分有近0.1的提升,后面的迭代效果就不明显了。迭代能提升一点MOS分,但是会带来运算量(即CPU load)的增加,真正用时做一次算法迭代就可以了。