Unsupervised Training in Speech Separation Task

硕士毕业之前看到了一些关于语音分离上进行无监督训练的文章,觉得很有意思,想在本篇文章中小结一下。语音前端的包含的诸多任务,比如增强,分离,去混,去回声等等,核心都在于解决如何从观测信号中生成目标信号这一问题。传统算法会根据不同的声学场景下进行数学建模,产生信号模型,之后引入一些假设先验进行求解。Deep Learning兴起之后,方法论逐渐变成了从人为生成的大量成对数据中使用网络直接进行信号生成或者中间变量的估计,比如mask等等。在此之下,了解求解问题对应的信号模型的另一个作用就是指导我们生成匹配的成对数据,用于网络训练,这一过程也叫数据模拟(data simulation)。人为的数据生成有至少两个显而易见的优势,一是理论上无穷的训练样本,二是可以拿到ground truth的参考信号(否则无法进行网络训练这一监督学习的过程)。而相对应的,也会遗留下一个所谓data mismatch的问题,即产品中模型需要在实录环境下工作,而训练阶段网络仅仅只见到了人工模拟的数据。对于语音前端中的诸多问题,我们很难从实录的信号中获取信号模型里对应的目标信号(这里不同于ASR的抄本,无法做人工标记),比如增强任务中的降噪语音,分离任务中的单说话人,去混任务中的early part等等。因此理论上如果能缓解数据层面的mismatch,是有希望继续提升前端模型在实际场景中的表现的。

Methods

上面写了一些文字,主要也是想引入unsupervised training这个主题。之所以网络难以使用real data进行训练或者优化,核心在于我们拿不到训练所需的参考信号或者标签。反过来思考,如果可以想办法从中获取到一些可用的参考信号/标签,那么就可以克服这个问题。如何从实录信号中获取这些信息呢?

一个非常直接的思路,用传统的信号方法获取!本站点前面有一些文章介绍了一些单,多通道的增强和分离算法,虽然效果上不及目前基于网络的模型,但是无监督这一性质却比较吸引人,且这些算法大多不存在所谓的data mismatch问题(这里解释一步,mismatch还是存在的,比如算法基于的信号模型和实际信号的产生过程存在mismatch)。参考文献部分列举的[1-5]全部是沿着这种思路,大的区分即在于使用的无监督算法不同。到这里其实做过ASR的同学对这个过程应该感到熟悉,因为它十分类似于GMM-DNN的hybrid模型。ASR的hybrid模型训练,依赖GMM模型对齐产生的状态级别(state level)序列,而在分离任务中,我们也可以借助无监督算法(比如空间聚类算法家族)产生一个初始的伪标签(文献中多称为pseudo targets)指导下一步的网络训练。

下面简单概括和评论一下文章[1-5],感兴趣的同学可以详细看一下论文。[1-5]全部使用的是多通道的无监督算法产生初始标签,这是在意料之中,因为只有在多通道下才有可供分辨的空间信息加以利用,背后的原理我在前面的相关文章中已经多次解释了。拿到初始伪标签之后,由于数据本身是多通道的,因此我们可以选择去训练单或者多通道的网络模型。文章[1-3]是基于Deep Clustering的思路,无监督算法主要用于生成binary mask标签。文章[1]使用的是cacgmm算法,将预先处理得到的ratio mask转换为binary mask后训练单通道的DPCL模型。在作者的测试集上(WSJ multi-channel simulation数据集),这种方法训练的模型配合MVDR波束得到的WER已经可以媲美使用oracle标签的结果。[2]的信号模型十分简单,只考虑了信号衰减和时间延迟,使用K-means算法对双通道数据的NPD(normalized phase difference)特征进行聚类产生的binary mask训练DPCL模型。这个思路和[1]十分类似,这里不做过多介绍,但是个人的一个看法是,如果考虑噪声和混响存在的情况,仅仅依赖K-means算法对NPD的聚类结果大概率是不靠谱的:(。[3]是MERL发的一篇文章,聚类算法采用的是如下过程,先对双通道的语音提取cosIPD和sinIPD特征,之后进行PCA降维,最后使用GMM进行聚类,利用贝叶斯公式计算出每个GMM成分的后验作为soft mask然后转换成DPCL需要的binary mask标签。不同于[1,2]的是,其在训练阶段使用了weighted版本的DPCL代价函数,引入的weight矩阵主要用于解决聚类算法产生的标签存在的不可靠问题,对于不可靠的标签赋予较弱的权重。从实验的结论上看,单纯依靠伪标签训练的单通道DPCL模型要比使用oracle的模型在SDR上有6个DB的差距。不过由于在实验结论中,其采用的无监督算法在双通道上可以产生比伪标签训练的单通道DPCL好的结果(差距1dB+),因此个人觉得如果增加多通道的DPCL模型,是可以拿到一个更好的结果的(不过由于这篇文章强调的是要bootstrap单通道模型,所以并没有增加这一实验)。

比较有意思的是较早的几篇文章均是在DPCL上做unsupervised training的相关实验,个人觉得原因有二:一是DPCL是最早提出的用于解决语音分离问题的基于深度学习的方法,因此比较具有代表性。其二是DPCL依赖的标签是类别标签(等效为binary mask),对于无监督算法而言,其不可避免的会产生不可靠的预测,因此量化成binary的结果可以提升伪标签的可靠性的。

[4-5]是同一个单位的文章,采用的是基于LGM(local gaussian modeling)模型的BSS算法,并使用KLD代价函数优化网络。网络端主要预测不同成分的TF mask,然后计算对应的相关矩阵,BSS端则依赖EM算法产生,最后用KLD去优化两者在LGM模型中产生的概率分布,使得网络端向BSS端靠近。[5]相对于[4]的改进在于使用了Mentoring-Reverse Mentoring的技巧提升BSS算法产生的伪标签质量。[4]中,使用LGM模型指导一次网络训练即结束,而在Mentoring-Reverse Mentoring中,会反复的将前一步指导训练的网络放到LGM之前再次生成伪标签指导下一轮训练,迭代下去。[4,5]均指出,其提出的方法要比[1]中的,使用cacgmm直接生成binary或者ratio的mask训练DPCL或者PIT的网络结果要好。虽然这两篇文章使用的训练数据量非常有限(仅仅1k),但是就方法本身还是有一些借鉴意义,即从distribution的角度去考虑设计代价函数优化网络,而非使用mask标签。

[6]是我自己非常欣赏的一篇文章,不得不感叹Paderborn在盲源分离空间聚类家族的理解之深入。和[1-5]使用伪标签指导网络的思路不同,[6]提出直接使用cacgmm的对数似然函数作为代价函数优化网络,下面介绍一下具体如何操作。首先回顾一下CACGMM对观测向量$\mathbf{z}_{tf} = \mathbf{y}_{tf} / \Vert \mathbf{y}_{tf} \Vert$的建模:

其中$K$表示高斯分量的个数,如果建模噪声分量的话,一般$K = C + 1$,$C$是说话人个数。依据CACG分布,有

高斯成分$k$的后验概率

作为对应分量(说话人或者噪声)的mask估计。空间聚类算法均在EM框架下依次迭代得到分布参数,$(3)$作为M步,E步估计$\mathbf{B}_{kf}$和$\pi_{kf}$如下:

正常情况下,经过E步和M步的迭代,式$(1)$中的似然值会递增,将所有TF-bin的似然值累加得到累计的似然值(下式取了对数)也会递增:

因此这背后有一个比较直观的逻辑,既然EM算法是通过E步和M步的迭代优化$(5)$式,为什么不可以直接使用$(5)$式作为代价函数对网络进优化呢?从实现的角度来说,$(5)$式仅依赖分布参数$\mathbf{B}_{kf}$和$\pi_{kf}$。如果我们使用网络估计出初始的$\gamma_{ktf}$(也就是mask),那么通过E步得到分布参数之后便可以计算出对数似然进行反向传播更新网络权重。实际上这就是文章[6]的提出的核心方法(虽然还提出了两个函数变体,但是和上述思路不冲突)。在理解完这背后的逻辑之后,我第一时间在CHiME4数据集上尝试了复现,也得到了理想的mask估计。实验过程中有几个问题强调如下:

  1. 实际在$(4)$式中计算$\mathbf{B}_{kf}$的时候,将$\mathbf{z}_{tf}^H \mathbf{B}_{kf}^{-1} \mathbf{z}_{tf}$视为单位矩阵$\mathbf{I}$,和在MVDR这些波束中的相关矩阵估计方法保持一致。
  2. 原始论文使用的配置为$K = 2$,对应speech和noise,实际中我使用$K = 1$($\gamma_{1,tf}$使用$1 - \gamma_{0, tf}$代替)也可以拿到正常的结果,且训练过程不需要进行permutation的对齐。
  3. 对数似然计算的时候,涉及到复数矩阵的行列式和逆的求解,我在这篇文章中介绍过解法link
  4. 做推断的时候,$\gamma_{0,tf}$和$\gamma_{1,tf}$是乱序的,所以需要在频率轴上做permutation的对齐。

个人欣赏文章[6]提出的方法是因为这配得上真正意义上的无监督训练,理想状态下,只需要获取大量的实录数据就可以直接进行网络训练。美中不足在于,如果在用于训练数据上CACGMM算法本身不能拿到正常的效果,那么将其用于网络训练一般也会失效,这就要求数据本身的空间区分性要好一些,在一些比较极端的情况下,比如收音设备距离生源较远,有较强的方向噪声等等,CACGMM本身聚类的结果也会有问题。所以建议感兴趣的同学在开始训练前,先从训练集挑个子集,验证一下CACGMM的效果再进行尝试。从工程的角度,个人觉得可以这种方法可以用于fine tune模型,比如当模型存在比较明显的数据不匹配现象时,可以收集一批数据对该网络进行微调等等。

Permutation Alignment

前面的一些文章在介绍空间聚类算法的时候都会提到频率轴上的permutation问题。这主要是由于聚类算法本身的属性导致的。空间聚类算法在每个频率(子带)上是独立的进行EM迭代的,即每个频率独立的维护它自己的分布参数,这就有可能导致这样的情况,在频率$f_0$的子带上(假设$K = 2$),$\gamma_{0,tf}$和$\gamma_{1,tf}$对应spk0和spk1的mask,但是在频率$f_1$的子带上,$\gamma_{0,tf}$和$\gamma_{1,tf}$对应的是spk1和spk0。这个问题在EM过程中不影响似然函数的计算,但是当我们进行后续处理,比如做波束的时候,就需要对全频带的mask进行一次调整,把spk0和spk1的mask调整到一起。理论上,如果频率分辨率为$F$,那么所有可能的组合为$K^F$个,因此,无法采用暴力的方式求解最佳的调整组合,需要设计专门的算法进行。下图[7]可以比较形象的看出permutation alignment的必要性,左边是对齐之前的结果,看起来杂乱无章,基本看不出语谱的轮廓,经过对齐算法处理之后得到右图,清晰了很多。

目前公开算法中采用比较多的是Paderborn的实现,原始代码托管在github上pb_bss项目中,但是略微混乱,我看完逻辑之后整理如下,方便大家快速的理解一下整体算法。对于频域$T \times F$的mask矩阵,算法不是直接作用于全部频带,二是从中间某个频带开始,依次向高频和低频拓展,这样做在我个人看来可能是出于高频的空间混叠现象比较严重,而低频比较容易受噪声干扰两个因素,造成聚类效果不佳。supported_plan中的三元组iter_num, beg_freq, end_freq表示当前频率范围进行调整的迭代次数和起止的频率下标。每个频带$[f_\text{beg}, f_\text{end}]$内,算法进行如下操作:

  1. 计算该频带内的mask均值向量$\mathbf{c}_k$
  2. 对该频带内的每个频率上,计算掩码$\mathbf{m}_{k,f}$和$\mathbf{c}_k$的cos距离,按照permutation组合相加,记录最优的组合方式并交换对应的掩码$\mathbf{m}_{k,f}$
  3. 对step2迭代iter_num

处理完所有罗列的频带$[f_{\text{beg}}, f_{\text{end}}]$之后算法停止。所以整体看下来,算法谈不上复杂,甚至有些看起来像工程的trick,就我自己的实践而言,大部分情况下都可以把permutation问题解决掉。还有一点需要注意的是,由于一般的语音在高频的能量比较弱,所以即使高频出现了一些混乱,结合波束之后产生的影响也未必很大,故而我们基本可以放心的使用该算法解决所谓置换问题。那么这个置换问题能不能采用其他方法解决呢,答案是可以的。最常见的方法就是通过给定一个有偏的初始化替代随机初始化方法,避免EM朝着随机的收敛方向进行,感兴趣的同学可以自行学习一下Paderborn如何使用所谓的GSS(guide source separation,在初始化上做工作避免了permutation,从而将聚类的结果和speaker对应了起来)方法在CHiME数据集上拿到SOTA的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import numpy as np

from scipy.optimize import linear_sum_assignment

supported_plan = {
257: [[20, 70, 170], [2, 90, 190], [2, 50, 150], [2, 110, 210],
[2, 30, 130], [2, 130, 230], [2, 0, 110], [2, 150, 257]],
513: [[20, 100, 200], [2, 120, 220], [2, 80, 180], [2, 140, 240],
[2, 60, 160], [2, 160, 260], [2, 40, 140], [2, 180, 280], [2, 0, 120],
[2, 200, 300], [2, 220, 320], [2, 240, 340], [2, 260, 360],
[2, 280, 380], [2, 300, 400], [2, 320, 420], [2, 340, 440],
[2, 360, 460], [2, 380, 480], [2, 400, 513]]
}


def norm_observation(mat: np.ndarray,
axis: int = -1,
eps: float = 1.0e-6) -> np.ndarray:
"""
L2 normalization for observation vectors
"""
denorm = np.linalg.norm(mat, axis=axis, keepdims=True)
denorm = np.maximum(denorm, eps)
return mat / denorm


def permu_aligner(masks: np.ndarray, transpose: bool = False) -> np.ndarray:
"""
Solve permutation problems for clustering based mask algorithm
Reference: "https://github.com/fgnt/pb_bss/tree/master/pb_bss"
Args:
masks: K x T x F
Return:
aligned_masks: K x T x F
"""
if masks.ndim != 3:
raise RuntimeError("Expect 3D TF-masks, K x T x F or K x F x T")
if transpose:
masks = np.transpose(masks, (0, 2, 1))
K, _, F = masks.shape
# normalized masks, for cos distance, K x T x F
feature = norm_observation(masks, axis=1)
# K x F
mapping = np.stack([np.ones(F, dtype=np.int) * k for k in range(K)])

if F not in supported_plan:
raise ValueError(f"Unsupported num_bins: {F}")
for itr, beg, end in supported_plan[F]:
for _ in range(itr):
# normalized centroid, K x T
centroid = np.mean(feature[..., beg:end], axis=-1)
centroid = norm_observation(centroid, axis=-1)
go_on = False
for f in range(beg, end):
# K x K
score = centroid @ norm_observation(feature[..., f], axis=-1).T
# derive permutation based on score matrix
index, permu = linear_sum_assignment(score, maximize=True)
# not ordered
if np.sum(permu != index) != 0:
feature[..., f] = feature[permu, :, f]
mapping[..., f] = mapping[permu, f]
go_on = True
if not go_on:
break
# K x T x F
permu_masks = np.zeros_like(masks)
for f in range(F):
permu_masks[..., f] = masks[mapping[..., f], :, f]
return permu_masks

这个算法其实是自己一直想花写笔墨介绍的,毕竟它和空间聚类算法高度绑定,在很多场景下都需要使用,这次结合着unsupervised training的主题就一起介绍了。第二小结介绍的六篇文章,个人比较推荐的是出自Paderborn的1和6,算是两种思路(伪标签和无监督代价函数)的代表,其中6的思路十分独特,个人觉得具备一定的应用前景,感兴趣的读者可以结合手头的需求进行尝试。

其实解决前端模型data mismatch还是有一些其他思路的,比如结合其他任务做联合训练(ASR,Speaker-ID等等)。对于ASR而言,前后端联合建模的优势我在前面的multi-channel AM一文中也谈到了,这里可以从另一个角度谈一下。信号层面我们虽然拿不到ground truth,但是对于其他任务是可以借助人工标记的,因此这种和其他任务做联合训练的思路其实也是一种用out of domain的标签克服in domain的标签缺失问题的方案,只是在这种情况下,前端生成的信号就未必是我们通常期待的了,因后端任务而异。

感觉最近前端的话题写的多了,后面还是得多更点ASR的文章,不然都快让人忽略我其实是个做识别的boy…

Reference

[1]. Drude L, Hasenklever D, Haeb-Umbach R. Unsupervised training of a deep clustering model for multichannel blind source separation[C]//ICASSP 2019-2019 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2019: 695-699.
[2]. Tzinis E, Venkataramani S, Smaragdis P. Unsupervised deep clustering for source separation: Direct learning from mixtures using spatial information[C]//ICASSP 2019-2019 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2019: 81-85.
[3]. Seetharaman P, Wichern G, Le Roux J, et al. Bootstrapping single-channel source separation via unsupervised spatial clustering on stereo mixtures[C]//ICASSP 2019-2019 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2019: 356-360.
[4]. Togami M, Masuyama Y, Komatsu T, et al. Unsupervised Training for Deep Speech Source Separation with Kullback-Leibler Divergence Based Probabilistic Loss Function[C]//ICASSP 2020-2020 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP). IEEE, 2020: 56-60.
[5]. Nakagome Y, Togami M, Ogawa T, et al. Mentoring-Reverse Mentoring for Unsupervised Multi-channel Speech Source Separation[J]. Proc. Interspeech 2020, 2020: 86-90.
[6]. Drude L, Heymann J, Haeb-Umbach R. Unsupervised training of neural mask-based beamforming[J]. arXiv preprint arXiv:1904.01578, 2019.
[7]. Sawada H, Araki S, Makino S. Underdetermined convolutive blind source separation via frequency bin-wise clustering and permutation alignment[J]. IEEE Transactions on Audio, Speech, and Language Processing, 2011, 19(3): 516-527.