上个月做了一下single-channel下supervised语音分离的两种比较经典的方法,DPCL和uPIT,略有感触。在这篇文章中,我结合我的实践和理解,对它们做一些简单介绍。
首先我再次用自己的语言介绍一遍mask,因为前几天突然有非语音相关的人突然问mask相关的概念。单通道的增强或者分离任务中的mask实际全称应该是TF-mask(TF表示time and frequency),定义在T-F域上。TF-mask被定义的前提通常是信号仅仅为加性混叠(分离任务,混叠信号为说话人,增强任务,混叠信号为语音和加性噪声),因此广泛应用于分离,增强,而非解混响等任务中。不做区分,定义混叠信号如下:
其中$\mathbf{s}_i$表示第$i$个说话人,$\mathbf{n}$表示加性噪声。变换到短时频域上:
其中$\mathbf{S}_i = \text{STFT}(\mathbf{s}_i), \mathbf{N} = \text{STFT}(\mathbf{n}) \in \mathbf{C}^{T \times F}$。
注:$\text{STFT}$表示短时傅里叶变换,其实这里面讲究的东西还是蛮多的,由于我也不确认我的每处理解都十分准确,因此这里不做仔细挖掘。但是有几个概念需要说明一下:
短时傅里叶变换之后的结果$\mathbf{S}$在复数域,我们将$|\mathbf{S}|$称为幅度谱(Magnitude Spectrum),$|\mathbf{S}|^2$称为功率谱(Power Spectrum),$\angle \mathbf{S}$称为相位谱。
使用mask的初衷是,希望通过TF-mask $\mathbf{M}_i$,最大程度还原出希望的语音信号$\mathbf{s}_i$。使用mask还原出的语音信号定义为(在“语音增强mask方法”中我也提到了):
得到还原的信号之后,根据不同的任务,就可以使用SNR,SDR或者WER来衡量质量高低了,一般在分离中使用SDR(signal distortion rate)指标。
目前常用的mask种类有IBM(binary mask),IAM(amplitude mask),IRM(ratio mask),PSM(phase sensitive mask),定义如下:
IBM:
IAM:
IRM:
PSM:
其中,$ \mathbf{M}_{\text{IBM}}^i \in {0, 1},\mathbf{M}_{\text{IRM}}^i \in [0, 1]$,范围均被限定,PSM可能出现负值。
关于IAM和IRM再多提几点:
IAM在汪老师的论文里面又被称为SMM(spectral magnitude mask)和FFT-mask,和IRM的关系可以表示为:
很多论文采用的loss函数表达为下面形式的:
可以等价为IAM为target。
IRM的定义式在俞栋老师和汪老师的论文中略有不同,上面给出的是俞栋老师uPIT论文中的表达式,汪老师的定义如下:
两者之间的关系为:
汪老师的综述详见:
- Wang D L, Chen J. Supervised Speech Separation Based on Deep Learning: An Overview[J]. IEEE/ACM Transactions on Audio Speech & Language Processing, 2018, PP(99):1-1.
在wsj0-mix2数据集上,上面四种mask oracle的分离效果如下(SDR)
Mask | FM | FF | MM | FF/MM | AVG |
---|---|---|---|---|---|
IAM | 12.49 | 12.73 | 11.58 | 11.88 | 12.19 |
IBM | 12.94 | 13.20 | 12.04 | 12.35 | 12.65 |
IRM | 12.86 | 13.14 | 11.96 | 12.27 | 12.57 |
PSM | 15.79 | 16.03 | 14.90 | 15.20 | 15.50 |
可以看出,oracle的情况下,PSM的优势确实很明显。
Label Permutation
label的置换问题是使用监督性学习解决说话人无关的语音分离问题首先要解决的问题。在增强任务中,我们只需要学习到speech的mask,而分离任务中,则需要学习到多个说话人的mask,由此引入了训练过程中的label置换问题。
置换问题怎么理解?假设网络结构存在两个线性层输出$\mathbf{m}_1$和$\mathbf{m}_2$,对应的label为$s_1, s_2$。这样就存在两种匹配方式,即$\mathbf{m}_1 \to s_1, \mathbf{m}_2 \to s_2$和$\mathbf{m}_1 \to s_2, \mathbf{m}_2 \to s_1$。由此拓展下去,$N$个说话人存在$N!$种对应方式。传统的训练方法,输出和label之间的对应关系是固定的,以增强网络为例,一个对应speech,另一个对应noise,网络收敛之后,label对应speech的输出speech的mask,对应noise的输出noise的mask。在分离任务中,训练语料存在多个说话人(多余网络输出个数),因此,不可能将网络的输出和确定的说话人对应起来,故而无法组织训练。
相关理解可以参考如何理解语音分离中的置换问题(permutaiton problem)
DPCL和uPIT就是从两个角度入手解决label的置换问题的。
DPCL
Deep Clustering没有正面和label permutation硬怼,而是绕过了这个问题,尝试将每个TF-bin映射到一个高维的特征上,使得其可以更好的被区分开来。这个embedding过程表示为:
得到embedding $\mathbf{v}_{tf}$之后,接一个分类算法,将类别转换为binary mask就可以根据$(2)$进行分离了。在oracle的情况下,将speaker的IBM one-hot编码的分类结果和IBM可以相互转换。
$(3)$式是对单个TF-bin进行的embedding,实际训练中输入以帧为单位,因此,进行的实际上是
的过程。
DPCL的loss function通过亲和性矩阵$\mathbf{A}^\top\mathbf{A}, \mathbf{A} \in \mathbf{R}^{D \times T}$定义,$\mathbf{A}$的列向量表示embedding,$(\mathbf{A}^\top\mathbf{A})_{ij} = 1$表示embedding $\mathbf{A}^\top_i$和$\mathbf{A}^\top_j$属于一个类别。
令$\mathbf{V} \in \mathbf{R}^{S \times F}$表示IBM的one-hot编码结果,$\mathbf{Y} \in \mathbf{R}^{D \times F}$表示DPCL输出embedding。loss function用两种embedding亲和性矩阵之差的F范数表示:
在具体实现的时候,以整句训练为例,$\mathbf{Y} \in \mathbf{R}^{D \times FT}$。这样$ \mathbf{Y}^\top\mathbf{Y} \in \mathbf{R}^{FT \times FT}$,显存很快就爆掉,可以采用等价计算方式:
进行。
下面提几个比较细节的地方:
- 计算$(4)$式的时候,原始论文应该是mask掉了silence的T-F bin,因此,注意计算的时候掩蔽掉silence部分。是否silence取决于每个TF bin的能量值,论文中将每个句子比最大能量值小40dB的bin认为成silence。
- DPCL收敛的loss比较大,我收敛结果是3700+每个TF-bin,参考一下。
- 训练和测试时候的特征一定要保证一致(是否log,是否cmvn等等),我开始时训练的cmvn代码逻辑有问题,测试时分离效果总是高低频对半分,查了一周多才找到问题所在……
- 测试时聚类的时候,最好也将silence部分掩蔽掉,提高聚类的效果。
整个实验下来,主要是上面的3耗了一点时间,其他过程均比较顺利。代码和结果可以参看deep-clustering。
DPCL这块我没有调过多的配置,目前存在的问题是batch训练的时候,效果会比逐句训练差不少(average SDR-impr 只能在8.5这样子),后面有空还要check一下问题所在。
Utterance Level PIT
PIT采取的方案就硬怼,既然pair方式有$N!$种,那就直接遍历一遍,把最小的当loss,强制让网络学习align过程和对应的mask。它最早提出的时候,loss定义在帧级别(frame level),但是因为不能保证不同时刻网络的mask和speaker的对应关系保持一致,因此需要加一个speaker tracking的后处理过程(本身论文并没有提出speaker tracking的算法,直接和target比算的oracle结果)。uPIT的u表示utterance level,loss定义在整个句子上,用RNN建模:
$\mathcal{P}$表示$S$个speaker的排列方案。把上述定义成为permutate loss。
uPIT遇到的坑主要是batch情况下,permutate loss的计算。如果N个句子同时训练,permutate loss是每个句子的permutate loss之和。我第一次实现写成了batch的permutate loss,训练不收敛。除此之外,其他过程也都顺风顺水。代码可以参见uPIT-for-speech-separation。
uPIT调了几组参数,主要是dropout,weight decay,mask类型,激活函数,输入特征等等,到目前为止总结实验结果如下:
从表中可以看出,dropout,weight decay等正则化手段对最终的结果提升影响较大,线性谱不做cmvn的效果最好,ReLU在不同的mask类型上表现均强于sigmoid,但是和论文结论不同的是,PSM并没有显示出相对IAM的优势,二者的结果相差不大。
最后再说一点感触吧,现在大家都在用DL做东西,框架也很方便,因此可能最多的时间可能都不是耗在代码实现,而是debug和调参这些比较枯燥的任务上。对于这些,还是将它看成一种能力比较好,网络不收敛,如何找出问题,或者应用trick,如何根据实验结果调整参数,使模型更优,如何(尽量)解释(分析)不同参数对模型的影响等等,这些都是通过不断的实验和观察,才能得出规律,因此,切忌浮躁,多做实验!