基于深度学习的单通道语音加强

本文代码请见:https://github.com/Ryuk17/SpeechAlgorithmshtml

博客地址(转载请指明出处):https://www.cnblogs.com/LXP-Never/p/14142108.htmlgit

若是你以为写得还不错,点赞👍,关注是对我最大的支持,谢谢😃github

  传统的语音加强方法基于一些设定好的先验假设,可是这些先验假设存在必定的不合理之处。此外传统语音加强依赖于参数的设定,人工经验等。随着深度学习的发展,愈来愈多的人开始注意使用深度学习来解决语音加强问题。因为单通道使用场景较多,本文就以单通道语音加强为例。网络

  目前基于深度神经网络单通道语音加强方法大体能够分为两类:oracle

  • 第一种基于映射的方法
  • 第二种基于mask的方法

基于映射的语音加强

  基于映射的语音加强方法经过训练神经网络模型直接学习带噪语音和纯净语音之间的映射关系,有两种映射方案:dom

频谱映射:使用模型预测语音的时频域表示,以后再将语音的时频域表示经过波形合成技术恢复成时域信号。使用最多的时频域表示特征是短时傅里叶变换谱,利用人耳对相位不敏感的特性,通常只预测短时傅里叶变换幅度谱,并使用混合语音的相位合成预测语音的波形。ide

波形映射:直接将带噪语音波形输入到模型,模型直接输出纯净语音波形的方法。函数


  咱们以频谱映射举例说明一下:性能

训练阶段

输入:这里采用较为简单地特征,即带噪声语音信号的幅度谱,也能够采用其余的特征。值得一提的是,若是你的输入是一帧,对应输出也是一帧的话效果通常不会很好。所以通常采用扩帧的技术,以下图所示,即每次输入除了当前帧外还须要输入当前帧的前几帧和后几帧。这是由于语音具备短时相关性,对输入几帧是为了更好的学习这种相关性学习

  • Label:数据的label为纯净语音信号的幅度谱,这里只须要一帧就够了。
  • 损失函数:学习噪声幅度谱与纯净语音信号的幅度谱相似于一个回归问题,所以损失函数采用回归经常使用的损失函数,如均方偏差(MSE)、均方根偏差(RMSE)或平均绝对值偏差(MAE)等....
  • 最后一层的激活函数:因为是回归问题,最后一层采用线性激活函数
  • 其余:输入的幅度谱进行归一化能够加速学习过程和更好的收敛。若是不采用幅度谱能够采用功率谱,要注意的是功率谱若是采用的单位是dB,须要对数据进行预处理,由于log的定义域不能为0,简单的方法就是在取对数前给等于0的功率谱加上一个很是小的数

加强阶段

  • 输入:输入为噪声信号的幅度谱,这里一样须要扩帧。对输入数据进行处理能够在语音信号加上值为0的语音帧,或者舍弃首尾的几帧。若是训练过程对输入进行了归一化,那么这里一样须要进行归一化
  • 输出:输入为估计的纯净语音幅度谱
  • 重构波形:在计算输入信号幅度谱的时候须要保存每一帧的相位信息,而后用保存好的相位信息和模型输出的幅度谱重构语音波形,代码以下所示。
spectrum = magnitude * np.exp(1.0j * phase)

基于Mask的语音加强

Mask这个单词有的地方翻译成掩蔽有的地方翻译成掩膜,我我的倾向于翻译成“掩蔽”,本文就用掩蔽做为Mask的翻译。

时频掩蔽

  咱们都知道语音信号能够经过时域波形或者频域的各类频谱表示,此外语谱图能够同时展现时域和频域的信息,所以被普遍应用,以下图所示。语谱图上的像素点就能够称为 时频单元

如今咱们假设有两段语音信号,一段是纯净信号,另外一段是噪声,他们混合在一块儿了,时域波形和对应的语谱图分别以下图所示:

若是咱们想将纯净语音信号从混合信号中抽离在时域方面是很难作到的。如今咱们从语谱图(语音的时频单元)角度入手去解决语音分离问题。首先咱们提出两个假设:

一、咱们假设信号能量稀疏的,即对于大多数时频区域它的能量为0,以下图所示,咱们能够看到大多数区域的值,即频域能量为0。

二、咱们假设信号能量不相交的,即它们的时频区域不重叠或者重叠较少,以下图所示,咱们能够看到时频区域不为0的地方不重叠或者有较少部分的重叠。

基于以上两点假设,咱们就能够分离咱们想要的信号和噪声信号。给可能属于一个信号源的区域分配掩码为1,其他的分配掩码0,以下图所示。

  咱们经过0和1的二值掩码而后乘以混合信号的语谱图就能够获得咱们想要喜爱的语谱图了,以下图所示。

  神经模型通常直接预测时频掩蔽$M(t,f)$,以后再经过$M(t,f)$与混合语音$Y(t,f)$相乘获得预测的纯净语音$\hat{S}(t,f)=\hat{M}(t,f)\otimesY(t,y)$,其中$\otimes$表明哈达玛乘积(Hadamard Product)。在语音加强研究的发展过程当中,研究人员提出了一系列的时频掩蔽做为训练目标:

理想二值掩蔽(Ideal Binary Mask,IBM)

原理:因为语音在时频域上是稀疏分布的,对于一个具体的时频单元,语音和噪声的能量差别一般比较大,所以大多数时频单元上的信噪比极高或极低。IBM 是对这种现实状况的简化描述,将连续的时频单元信噪比离散化为两种状态 1 和0,在一个时频单元内:若是语音占主导(高信噪比),则被标记为 1;反之若是噪声占主导(低信噪比),则标记为 0。最后将 IBM 和带噪语音相乘,实际上就是将低信噪比的时频单元置零,以此达到消除噪声的目的。

  所以,IBM 的值由时频单元上的信噪比SNR(t,f)和设定的阈值比较以后决定:

$$公式1:I B M(t, f)=\left\{\begin{array}{l}
1, \operatorname{SNR}(t, f)>L C \\
0, \text { else }
\end{array}\right.$$

其中LC为阈值,通常取0,SNR计算公式为:

$$公式2:\operatorname{SNR}(t, f)=10 * \log 10\left(\frac{|S(t, f)|^{2}}{|N(t, f)|^{2}}\right)$$

  • 优势:IBM 做为二值目标,只须要使用简单的二分类模型进行预测,而且能够有效地提升语音的可懂度。
  • 缺点:IBM 只有 0 和 1 两种取值,对带噪语音的处理过于粗暴,处理过程当中引入了较大的噪声,没法有效地改善语音质量。

我看到过不少种写法

def IBM(clean_speech, noise):
    """计算 ideal binary mask (IBM)
    Erdogan, Hakan, et al. "Phase-sensitive and recognition-boosted speech separation using deep recurrent neural networks." ICASSP, 2015.
    :param clean_speech: 纯净语音 STFT
    :param noise: 噪声 STFT
    :return: 纯净语音的理想二值掩膜 IBM
    """
    mask = np.zeros(np.shape(clean_speech), dtype=np.float32)
    mask[np.abs(clean_speech) >= np.abs(noise)] = 1.0
    return mask

第二种

def IBM_SNR(clean_speech, noise_speech):
    """计算 ideal binary mask (IBM)
    Erdogan, Hakan, et al. "Phase-sensitive and recognition-boosted speech separation using deep recurrent neural networks." ICASSP, 2015.
    :param clean_speech: 纯净语音 STFT
    :param noise_speech: 带噪语音 STFT
    :return: 纯净语音的理想二值掩膜 IBM
    """
    _eps = np.finfo(np.float).eps  # 避免除以0
    theta = 0.5  # a majority vote
    alpha = 1  # ratio of magnitudes
    mask = np.divide(np.abs(clean_speech) ** alpha, (_eps + np.abs(noise_speech) ** alpha))
    mask[np.where(mask >= theta)] = 1
    mask[np.where(mask < theta)] = 0
    return mask
第二种

第三种

def IBM_SNR(clean_speech, noise_speech,delta_size):
    """计算 ideal binary mask (IBM)
    Erdogan, Hakan, et al. "Phase-sensitive and recognition-boosted speech separation using deep recurrent neural networks." ICASSP, 2015.
    :param clean_speech: 纯净语音 STFT
    :param noise_speech: 带噪语音 STFT
    :return: 纯净语音的理想二值掩膜 IBM
    """
    _eps = np.finfo(np.float).eps  # 避免除以0
    local_snr = 0
    ibm  = np.where(10. * np.log10(np.abs(clean_speech) ** 2 / np.abs(noise_speech) ** 2) >= local_snr, 1., 0.)

    if delta_size > 0:
        ibm = ibm[:, delta_size: -delta_size]
    return ibm
View Code

理想浮值掩蔽(Ideal Ratio Mask,IRM)

原理:基于语音和噪声正交,即不相关的假设下,即$S(t,f) ⋅ N(t,f) = 0$,IRM直接刻画了时频单元内纯净语音能量和带噪语音能量的比值,是目前使用很是普遍的一种掩蔽方法。

  在这个假设下带噪语音的能量能够表示为:

$$公式2:|\boldsymbol{Y}(t, f)|^{2}=|\boldsymbol{S}(t, f)+\boldsymbol{N}(t, f)|^{2}=|\boldsymbol{S}(t, f)|^{2}+|\boldsymbol{N}(t, f)|^{2}$$

所以获得 IRM 为:

$$公式3:I R M(t, f)=\left(\frac{|S(t, f)|^{2}}{|Y(t, f)|^{2}}\right)^{\beta} =\left(\frac{|S(t, f)|^{2}}{|S(t, f)|^{2}+|N(t, f)|^{2}}\right)^{\beta}$$

其中$\beta$为可调节尺度因子,通常取0.5。IRM取值在 0 到 1 之间,值越大表明时频单元内语音占的比重越高。另外,IRM 的平方形式就是经典的维纳滤波器(Wiener Filter),它是均方偏差意义上的最优滤波器。

  • 优势:IRM 是分布在 0 到 1 的连续值,所以 IRM 是对目标语音更加准确的刻画,这使得 IRM 能够有效地同时提高语音的质量和可懂度。
  • 缺点:使用未处理的相位信息进行语音重构(相位对于感知质量也很重要)
def IRM(clean_speech, noise):
    """计算Compute ideal ratio mask (IRM)
    "Phase-sensitive and recognition-boosted speech separation using deep recurrent neural networks," in ICASSP 2015, Brisbane, April, 2015.
    :param clean_speech: 纯净语音 STFT
    :param noise: 噪音 STFT
    :return: 在原始音频域中分离(恢复)的语音
    """
    _eps = np.finfo(np.float).eps  # 防止分母出现0
    mask = np.abs(clean_speech) / (np.abs(clean_speech) + np.abs(noise) + _eps)
    return mask


def Wiener_like(clean_speech, noise):
    """计算Wiener-like Mask
    "Phase-sensitive and recognition-boosted speech separation using deep recurrent neural networks," in ICASSP 2015, Brisbane, April, 2015.
    :param clean_speech: 纯净语音 STFT
    :param noise: 噪音 STFT
    :return: 在原始音频域中分离(恢复)的语音
    """
    _eps = np.finfo(np.float).eps  # 防止分母出现0
    mask = np.divide((np.abs(clean_speech) ** 2 + _eps),
                     (np.abs(clean_speech) ** 2 + np.abs(noise) ** 2) + _eps)
    return mask

理想幅度掩蔽(Ideal Amplitude Mask,IAM)

原理:IAM也称为Spectral Magnitude Mask(SMM),不对噪声和语音作出正交假设,IAM刻画的也是纯净语音和带噪语音的能量比值

$$公式4:\operatorname{IAM}(t, f)=\frac{|S(t, f)|}{|Y(t, f)|}$$

因为在语音和噪声叠加的过程当中,存在反相相消的状况,所以并不能保证带噪语音的幅值老是大于纯净语音的幅值,所以 IAM 的范围是$[0,+\infty ]$。若是目标中出现很是大的数值,会致使训练过程出现异常。为了稳定训练,通常会将 IAM 进行截断到必定的范围内。为了肯定合适的截断范围,咱们能够在训练数据上采样 100 句语音并计算 IAM,就能够对IAM 的数值范围获得一个近似的估计,获得如图 3.4 的结果。通常将 IAM 截断到[0, 1]或者[0, 2]便可,由于只有很是少部分的 IAM 落在了$[2,+\infty ]$的区间内。

图* IAM数值分布直方图

def IAM(clean_speech, noise_speech):
    """计算ideal amplitude mask (IAM)
    "Phase-sensitive and recognition-boosted speech separation using deep recurrent neural networks," in ICASSP 2015, Brisbane, April, 2015.
    :param clean_speech: 纯净语音 STFT
    :param noise_speech: 带噪语音 STFT
    :return:
    """
    _eps = np.finfo(np.float).eps  # 避免除以0
    mask = np.abs(clean_speech) / (np.abs(noise_speech) + _eps)
    return mask

相位敏感掩蔽(Phase Sensitive Mask,PSM)

原理:PSM考虑到相位偏差的时频掩蔽

  PSM在形式上是 IAM 乘上纯净语音和带噪语音之间的余弦类似度

$$公式5:P S M(t, f)=\frac{|S(t, f)|}{|Y(t, f)|} \cos \left(\theta^{S}-\theta^{Y}\right)$$

式中$\theta^{S}-\theta^{Y}$表示纯净语音和带噪语音的相位差,不难看出,PSM 的取值范围是$[-\infty,+\infty]$,所以也须要截断,咱们一样使用直方图统计PSM的数值分布范围,从下图能够看出在0 和 1 附近出现两个明显的峰值,这也再次说明了 IBM 目标设计的合理性。为了方便,通常将 PSM 截断到[0, 1],或者是适当将截断的区间放大到[-1, 2]。

 PSM数值分布直方图

  • 优势:纯净语音相位和带噪语音相位的差别,加入相位信息以后,PSM方法能够得到更高的SNR,于是降噪效果比IAM更好。
def PSM(clean_speech, noise_speech):
    """计算ideal phase-sensitive mask (PSM)
    :param clean_speech: 纯净语音 STFT
    :param noise_speech:带噪语音 STFT
    :return:
    """
    _eps = np.finfo(np.float).eps  # 防止分母出现0
    clean_speech_phase = np.angle(clean_speech)
    noise_speech_phase = np.angle(noise_speech)
    mask = np.abs(clean_speech) / np.abs(noise_speech) * np.cos(clean_speech_phase - noise_speech_phase)
    # Truncated Phase Sensitive Masking
    # Theta = np.clip(np.cos(clean_speech_phase-noise_speech_phase), a_min=0., a_max=1.)
    # mask = np.divide(np.abs(clean_speech), _eps + np.abs(noise_speech)) * Theta
    return mask

复数理想浮值掩蔽(Complex Ideal Ratio Mask,cIRM)

参考文献:2015_Complex ratio masking for monaural speech separation

原理:在复数域的理想浮值掩膜,同时加强幅度谱和相位谱

$条件:\left\{ \begin{array}{l}Y = {Y_r} + i{Y_i}\\M = {M_r} + i{M_i}\\S = {S_r} + i{S_i}\\{S_{t,f}} = {M_{t,f}}*{Y_{t,f}}\end{array} \right.$==>${S_r} + i{S_i} = ({M_r} + i{M_i})*({Y_r} + i{Y_i}) = ({M_r}{Y_r} - {M_i}{Y_i}) + i({M_r}{Y_i} + {M_i}{Y_r})$,

那么:$\left\{ \begin{array}{l}{S_r} = {M_r}{Y_r} - {M_i}{Y_i}\\{S_i} = {M_r}{Y_i} + {M_i}{Y_r}\end{array} \right.$  解方程得:$\left\{ \begin{array}{l}{M_r} = \frac{{{Y_r}{S_r} + {Y_i}{S_i}}}{{Y_r^2 + Y_i^2}}\\{M_i} = \frac{{{Y_r}{S_i} - {Y_i}{S_r}}}{{Y_r^2 + Y_i^2}}\end{array} \right.$

最终:$M_{cIRM} = {M_r} + i{M_i} = {\frac{{{Y_r}{S_r} + {Y_i}{S_i}}}{{Y_r^2 + Y_i^2}}} + i\frac{{{Y_r}{S_i} - {Y_i}{S_r}}}{{Y_r^2 + Y_i^2}}$

式中,$Y$是带噪语音,$S$是纯净语音。

  • 优势:cIRM可以同时加强嘈杂语音的幅度和相位响应,cIRM是加性噪声假设下的最优掩蔽,能够从带噪语音中完美重构纯净语音信号
def cIRM(clean_speech, noise_speech):
    """使用复理想比率掩码将语音从源信号的短时傅里叶变换和混合源信号的短时傅里叶变换中分离出来
    :param clean_speech:纯净语音
    :param noise_speech:带噪语音
    :return:
    """
    cIRM_r = (np.real(noise_speech) * np.real(clean_speech) + np.imag(noise_speech) * np.imag(clean_speech)) / \
             (np.real(noise_speech) ** 2 + np.imag(noise_speech) ** 2)

    cIRM_i = (np.real(noise_speech) * np.imag(clean_speech) - np.imag(noise_speech) * np.real(clean_speech)) / \
             (np.real(noise_speech) ** 2 + np.imag(noise_speech) ** 2)

    mask = cIRM_r + cIRM_i * 1j
    return mask

总结

  语音加强中的大部分掩蔽类方法,均可以当作在特定的假设条件下cIRM 的近似。若是将 cIRM 在直角坐标系下分解,cIRM 在实数轴上的投影就是 PSM。若是再将 cIRM在极坐标系下分解,cIRM 的模值就是 IAM。而 IRM 又是 IAM 在噪声语音不相关假设下的简化形式,IBM 则能够认为是 IRM 的二值版本。

各类理想掩蔽的性能比较

度量 IBM IRM IAM PSM cIRM
PESQ 2.47 3.33 3.45 3.71 4.49
STOI 0.91 0.94 0.97 0.97 1

  从上表中咱们能够看到 cIRM 能够实现对纯净语音几乎无损地重构,其余掩蔽因为进行了某些特定的假设,因此都会在必定程度上形成性能损失。虽然 cIRM 是最优掩蔽,可是使用其余简化的掩蔽方法能够下降预测的难度。这也是早期的语音加强研究选择使用 IBM 或者是 IRM 等简单掩蔽目标的缘由。在模型容量有限的状况下,cIRM 常常并非最好的选择,选择和模型建模能力匹配的目标才能得到最优的加强性能。

题外话

  可是,这里存在一个问题,咱们没法从语谱图中还原语音信号。为了解决这一问题,咱们首先还原全部的频率份量,即对二值掩码作个镜像后拼接。假设咱们计算语谱图时使用的是512点SFTF,咱们通常去前257点进行分析和处理,在这里咱们将前257点的后255作镜像,而后拼接在一块儿获得512点频率份量,以下图所示。

而后根据这个还原语音信号。这里指的一提的是,在进行STFT后的相位信息要保存,用于还原语音信号。

基于掩蔽的语音加强和基于映射的语音加强模型训练和加强过程相似,这里只提几个重要的地方,其他地方参考上面内容。

  • Label:数据的label为根据信噪比计算的IBM或者IRM,这里只须要一帧就够了
  • 损失函数:IBM的损失函数能够用交叉熵,IRM的损失函数仍是用均方差
  • 最后一层的激活函数:IBM只有0和1两个值,IRM范围为[0,1],所以采用sigmoid激活函数就能够了
  • 重构波形:首先用噪声幅度谱与计算的Mask值对应位置相乘,代码以下,而后根据相位信息重构语音波形。
enhance_magnitude = np.multiply(magnitude, mask)

Demo效果以及代码

首先看下实验效果,首先是基于映射语音加强的结果:

基于IBM语音加强的结果:

 

基于IRM语音加强的结果:

 

训练代码:

"""
@FileName: IBM.py
@Description: Implement IBM
@Author: Ryuk
@CreateDate: 2020/05/08
@LastEditTime: 2020/05/08
@LastEditors: Please set LastEditors
@Version: v0.1
"""import numpy as np
import librosa
from sklearn.preprocessing import StandardScaler
from keras.layers import *
from keras.models import Sequential
​
​
def generateDataset():
    mix, sr = librosa.load("./mix.wav", sr=8000)
    clean,sr = librosa.load("./clean.wav",  sr=8000)
​
    win_length = 256
    hop_length = 128
    nfft = 512
​
    mix_spectrum = librosa.stft(mix, win_length=win_length, hop_length=hop_length, n_fft=nfft)
    clean_spectrum = librosa.stft(clean, win_length=win_length, hop_length=hop_length, n_fft=nfft)
​
    mix_mag = np.abs(mix_spectrum).T
    clean_mag = np.abs(clean_spectrum).T
​
​
    frame_num = mix_mag.shape[0] - 4
    feature = np.zeros([frame_num, 257*5])
    k = 0
    for i in range(frame_num - 4):
        frame = mix_mag[k:k+5]
        feature[i] = np.reshape(frame, 257*5)
        k += 1
​
    snr = np.divide(clean_mag, mix_mag)
    mask = np.around(snr, 0)
    mask[np.isnan(mask)] = 1
    mask[mask > 1] = 1
​
    label = mask[2:-2]
​
    ss = StandardScaler()
    feature = ss.fit_transform(feature)
    return feature, label
​
​
def getModel():
    model = Sequential()
    model.add(Dense(2048, input_dim=1285))
    model.add(BatchNormalization())
​
    model.add(LeakyReLU(alpha=0.1))
    model.add(Dropout(0.1))
​
    model.add(Dense(2048))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.1))
    model.add(Dropout(0.1))
​
    model.add(Dense(2048))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.1))
    model.add(Dropout(0.1))
​
    model.add(Dense(257))
    model.add(BatchNormalization())
    model.add(Activation('sigmoid'))
    return model
​
def train(feature, label, model):
    model.compile(optimizer='adam',
                  loss='mse',
                  metrics=['mse'])
    model.fit(feature, label, batch_size=128, epochs=20, validation_split=0.1)
    model.save("./model.h5")
​
def main():
    feature, label = generateDataset()
    model = getModel()
    train(feature, label, model)
​
​
if __name__ == "__main__":
    main()

加强代码:

"""
@FileName: Inference.py
@Description: Implement Inference
@Author: Ryuk
@CreateDate: 2020/05/08
@LastEditTime: 2020/05/08
@LastEditors: Please set LastEditors
@Version: v0.1
"""import librosa
import numpy as np
from basic_functions import *
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from keras.models import load_model
​
def show(data, s):
    plt.figure(1)
    ax1 = plt.subplot(2, 1, 1)
    ax2 = plt.subplot(2, 1, 2)
    plt.sca(ax1)
    plt.plot(data)
    plt.sca(ax2)
    plt.plot(s)
    plt.show()
​
​
model = load_model("./model.h5")
data, fs = librosa.load("./test.wav", sr=8000)
​
win_length = 256
hop_length = 128
nfft = 512
​
spectrum = librosa.stft(data, win_length=win_length, hop_length=hop_length, n_fft=nfft)
magnitude = np.abs(spectrum).T
phase = np.angle(spectrum).T
​
frame_num = magnitude.shape[0] - 4
feature = np.zeros([frame_num, 257 * 5])
k = 0
for i in range(frame_num - 4):
    frame = magnitude[k:k + 5]
    feature[i] = np.reshape(frame, 257 * 5)
    k += 1
​
ss = StandardScaler()
feature = ss.fit_transform(feature)
mask = model.predict(feature)
mask[mask > 0.5] = 1
mask[mask <= 0.5] = 0
​
fig = plt.figure()
plt.imshow(mask, cmap='Greys', interpolation='none')
plt.show()
plt.close(fig)
​
magnitude = magnitude[2:-2]
en_magnitude = np.multiply(magnitude, mask)
phase = phase[2:-2]
​
en_spectrum = en_magnitude.T * np.exp(1.0j * phase.T)
frame = librosa.istft(en_spectrum, win_length=win_length, hop_length=hop_length)
​
show(data, frame)
librosa.output.write_wav("./output.wav",frame, sr=8000)

 

参考

【论文】2020_李劲东_基于深度学习的单通道语音加强研究

【博客文章】DNN单通道语音加强(附Demo代码)

【博客文章】基于Mask的语音分离

【github代码】speech-segmentation-project/masks.py

【github代码】ASP/MaskingMethods.py

【github代码】DC-TesNet/time_domain_mask.py

【github代码】ASC_baseline/compute_mask.py

值得作一作的项目:

相关文章
相关标签/搜索