参考
《A Hybrid DSP/Deep Learning Approach to Real-Time Full-Band Speech Enhancement》
《A Perceptually-Motivated Approach for Low-Complexity, Real-Time Enhancement of Fullband Speech》
注:由于文章包含许多LaTeX公式,可能需要反复刷新才能渲染成功,或者使用google浏览器。
Python 实现
见:https://github.com/AestheTech163/PercepNet
摘要
? ? ? ? 过去语音增强主要以谱减、谱估计方法对复数谱的幅值调整;近年来基于深度学习的主流语音增强,是直接调整短时傅里叶频谱,这导致计算量过大。下面介绍的PercepNet可以用非常少的计算量,达到实时地、高质量地语音增强;其技术以人类的听觉感知、语音的周期性为基础。
2 信号模型
带噪语音信号的模型为:
进一步,可以将干净的语音信号分解为局部周期分量和随机分量,即有。目标是构造一个强化的信号在听觉感知上尽可能接近(这是核心思想)。随机分量由于其近乎完全随机,而环境噪声也有天然的随机性,因此将干净的随机分量从受随机噪声污染的信号中分离出来是不可能的。不过,我们只需要的随机部分听起来像即可。(比如生成两段高斯白噪声,它们自然是完全不同的,但是听起来是一样的感觉)可以通过将滤波成和一样的频谱包络即可。另一方面,环境噪声不具有强周期性,因此可以较容易地将周期分量分离出来。再次,还是可以通过使得的基频和频谱包络逼近即可。
小结一下,我们尝试构造一个增强的语音信号,它满足如下两个条件:1)它有和干净语音相同的频谱包络,2)它在每个频率分量上都具有和干净语音信号相同的周期-随机比率;并且以人对频率的感知粒度(ERB子带)进行构造。
2.1 子带
大部分的噪声具有宽频带,并且频谱较为平滑;语音的周期和非周期分量都具有平滑的频谱包络,这使得我们只需 34 个子带就可以表示 0~20k Hz范围的频谱。(类似于求积分,函数曲线(包络)变化的越剧烈,要达到同样的积分精度,就需要更细致地划分积分区间;因此越平滑,越利于使用少量子带表达频谱)不过,这些子带不是等距的,而是根据人类听觉的等效矩形带宽来分布的。
2.2 增益
为了在每个子带得到和干净频谱相同的包络,我们使用如下理想率掩码作用到 带噪语音的子带上,就能使得增强信号的(每个子带的)能量等于干净信号的能量。
当语音只包含随机分量时,作用到语音第个子带上去,按照上面的讨论,就会得到和干净的随机分量难以区分的增强信号;另一方面,当语音只包含周期分量时,作用得到的增强语音会比干净语音要粗糙一点,这是因为我们使用的是子带,谐波间隔小于子带粒度,所以谐波间的噪声是无法去除的,尽管它们的能量一样(参考亚马逊的官方博客或者我根据其整理的笔记https://github.com/AestheTech163/PercepNet/tree/main/follow_AMS_blog)。为此,我们需要梳状滤波器Comb Filter来滤除基频的谐波间噪声。
3 音高滤波
我们使用基于基频的梳状滤波(Comb Filtering),对基频的谐波间噪声形成破坏性干扰;对于有限的帧长(20ms)梳状滤波可以达到比STFT精细得多的频谱滤波。为了估计基频,我们使用一种基于相关函数和动态规划的算法(对此,我基于google开源算法,改造、简化了一版实时基频估计算法,见:https://github.com/AestheTech163/TaoSbeechDSP/tree/main/f0tracker)。
3.1 滤波
在Rnnoise中就已经使用过一个最简单的梳状滤波器,可以将谐波间噪声降低 3dB;在这里,我们将使用更强的、非因果的梳状滤波,其传递函数为:
经过滤波后的噪声部分以系数进行减弱,这里做下简单解释:因为周期分量在前后多个以周期为间隔的片段上是相同的,因此使用使用进行加权平均后,仍然是原来的值,即能量不变;我们使用表示随机分量滤波后的信号,即,因而经过滤波后,其能量为:
这里用到了,在不同时刻的非相关性(言外之意还有是 0 均值),因此上面的第二项为 0;以及噪声的平稳性使得第一项的期望可以拿到求和外面来。
对于作者使用了Hann窗口,虽然没有矩形窗那样得到最大的噪声抑制效果,但是由于人的听觉系统掩蔽效应,残存的噪声实际上听起来比理论上更弱。当 M=5 时,减弱了,实际中,会根据允许的最大延迟对 Comb 窗口进行截断。梳状滤波在时域进行,结果记为,其STFT记为。
3.2 滤波强度
梳状滤波的强度非常重要:强度不足,则谐波间噪声导致声音听起来有些糊;强度太大,则会导致声音太机械化(事实上,这也是PercepNet死磕了Rnnoise中提出的观点,即这个强度比较难确定,因此这也是PercepNet的核心)。这里,作者设计了可学习的目标,即 周期-随机比,这使得我们可以利用神经网络来学习和预测。
定义“音高相干 pitch-coherence” 为信号的复数谱与其周期分量之间的余弦距离,这个量是对每个子带单独计算的,为了保持简洁我们略去子带下标;例如:干净语音的音高相干为:
同样我们可以定义带噪语音的音高相干为;考虑到是由带噪语音经过因子抑制得到的,那么估计量的音高相干可以由下式来近似:
观察分子分母同除以,可以发现是的单调增函数,且当语音中不含随机分量时有。
接下来,我们定义音高滤波强度为,用和的凸组合作为音高增强后的信号,目的使得成立,这也就是上面想达成的第二个条件。当时则没有滤波,当时则完全使用估计,因此的音高相干满足。若干净语音的音高相干在该区间中,则有解,此时方程如下:
3.2.1 滤波强度的求解
下面我们对方程的求解做一些讨论,由内积和范数的齐次性,对式左边分子分母同乘以,并令得:
再次由内积的线性性,并且考虑到是一个实数,可以拿到运算外面,得到:
由的定义,代入上式化为:
两边平方,得:
整理成关于的一元二次方程:
由求根公式,并略去负根得:
其中:
最后,由前面求得?,这就是最终的结果。
不过,我推导的结果似乎和论文中有些不同,要想得到和论文一致的结果,需要在上式中令,且令。
上面第一项可以认为近似成立,因为在发浊音时,带噪语音中以周期分量为主;因而在能量上和相近。
对于第二项,假设浊音中的周期分量和随机分量(包含环境噪声)是加性的,考虑,则,且。
因此,有:
同样,有:
对比上两式的不同的部分的绝对值大小,由 Cauchy 不等式得:
又是一个很小的数,那么当较小时,上两式中的不同的部分的绝对值就会很小,以至于可以忽略。
最后,结合实践,我得出以下结论:
1)经过上面的讨论,原论文中的近似值是合理的,至少是我可以理解的。
2)事实上,我在 PercepNet 的 Python 复现中,对比了原论文的公式,和上面推导的严格公式,实践证明对模型的结果没多大影响。
3)严格公式虽然计算上稍稍复杂了一点,但是由于这些计算仅仅发生在训练阶段,因此我建议直接使用严格公式。
接着原文讨论
在噪声水平很高的环境下,有可能我们估计的周期分量的音高相干本身比干净语音的音高相干还要低;此时无解,我们能做的最好的就是令,且计算一个增益修正因子,定义如下:
在条件下,并且在最坏的情况下:干净语音包含纯周期分量(),且噪声很大,使得,此时;因此,最大抑制为,此值为噪声对音调掩蔽效应的阈值。
4 DNN 模型
4.0 输入和输出
输入特征包括:幅度谱的 34 个子带、34 个子带的音高相干、1 个基频、一个基频置信度(相关值)共 70 个特征。其中 34 个幅度谱子带是相对当前的未来第 M 帧的,而 34 个音高相干是相对当前帧计算的,但是在其计算周期分量时,已经利用了未来的数据,所以音高相干只能用当前帧的(算法的延迟是受限的)。
4.0.1 关于基频特征的讨论
这里再做一些额外的讨论:作者使用了一个标量 F0 来表示音高信息,一个标量给到的直接信息太少了,我觉得这不是最好的 pitch-representation,虽然基频(指的是音高)对于计算机来说只是一个标量,但是对于我们来说,基频还意味着各次谐波;因此,我尝试了两种 pitch-representation,分别是:
1)Pitch Embedding
使用深度学习框架的 Embedding 层,但该方法有频率分辨率问题。
2)Continuous Pitch Representation(CPR)
原论文上的频响图就是一例子,例如:对于 200 Hz的基频,可以将其展开成如下200Hz的频响图曲线(下图 1);但既然手动构造一个曲线,也没必要使用这么复杂的曲线,因此我使用了 warped-cos 曲线(下图 2),该方法没有频率分辨率问题。
CPR 的计算过程,其中为对应基频的相关强度,我们直接将其标量乘到表示向量上去,这相当于使用基频置信度对生成的 d 维空间向量进行伸缩,如下:
上面两种表示都是将标量展开成向量,虽然我们不知道每一帧准确的频谱包络,但是给定基频之后,我们可以肯定某些频点的能量不应该很大,谐波频点的能量有很大概率不会太低,因此期望展开的 representation 能给到模型更多的信息。
再讨论原论文使用一个标量:1)或许神经网络可以学习到基频的某种连续表示,但这也需要消耗大量神经元;2)另一方面,一个标量也可以看作 d 维向量空间中的一维子空间,而不同的基频输入就意味着在这个子空间轴上来回滑动,只利用向量的大小而忽略向量的方向,显然不利于神经网络进行区分;而上面的 CPR 表示,就类似于“词向量word embedding” 给不同的单词以不同的方向,只不过在那里是学习到的方向,而这里是手动构造的。
回到原论文:输出层对每个子带计算两个量:;用于先音高滤波:,再进行噪声抑制:。
4.1 训练数据
训练数据,这里不再讨论;我在实践中使用的信噪比是范围,参考 DNS 官方的脚本来构造数据集。
4.2 损失函数
如上式,损失函数分为两个部分:关于增益损失,作者采用了响度域损失(声音的感知响度与能量的某次方呈比例);实际上分析下来,和时对比,当时,因为增益小于 1,所以之间的误差被放大了。另又添加了一个 4 次项,来进一步惩罚较大误差。
由于函数 sqrt 的非线性,使得损失在时,比在时的损失更大,在前者情况下,噪声比较大,此时的音高滤波对降低谐波间噪声很重要,因此此时的误差更需要惩罚;后者情况下,噪声很小,那么进行多强的音高滤波其实得到的结果都差不多,因而误差大点也没太大关系。
5 包络后滤波
为了进一步抑制噪声、增强语音,我们对 DNN 的输出做一些调整:对噪声更大的子带进一步减小增益系数,另一方面增大干净子带以补偿能量,后滤波公式为:,其图像如下,可以发现函数将区间挤压到很小的范围;事实上由函数在 0 点的 Taylor 展开可知当时,是的平方高阶小。
最后,为了进一步补偿由 warp 函数滤波造成信号整体的能量降低,再添加一项与子带无关的整体补偿项,它作用到所有的增益上去:
其中:是使用模型输出增益抑制后信号总能量,而是使用 warped 增益抑制后的信号总能量。