本文代码请见:https://github.com/Ryuk17/SpeechAlgorithmshtml
博客地址(转载请指明出处):https://www.cnblogs.com/LXP-Never/p/14142108.htmlgit
若是你以为写得还不错,点赞👍,关注是对我最大的支持,谢谢😃github
传统的语音加强方法基于一些设定好的先验假设,可是这些先验假设存在必定的不合理之处。此外传统语音加强依赖于参数的设定,人工经验等。随着深度学习的发展,愈来愈多的人开始注意使用深度学习来解决语音加强问题。因为单通道使用场景较多,本文就以单通道语音加强为例。网络
目前基于深度神经网络单通道语音加强方法大体能够分为两类:oracle
基于映射的语音加强方法经过训练神经网络模型直接学习带噪语音和纯净语音之间的映射关系,有两种映射方案:dom
频谱映射:使用模型预测语音的时频域表示,以后再将语音的时频域表示经过波形合成技术恢复成时域信号。使用最多的时频域表示特征是短时傅里叶变换谱,利用人耳对相位不敏感的特性,通常只预测短时傅里叶变换幅度谱,并使用混合语音的相位合成预测语音的波形。ide
波形映射:直接将带噪语音波形输入到模型,模型直接输出纯净语音波形的方法。函数
咱们以频谱映射举例说明一下:性能
输入:这里采用较为简单地特征,即带噪声语音信号的幅度谱,也能够采用其余的特征。值得一提的是,若是你的输入是一帧,对应输出也是一帧的话效果通常不会很好。所以通常采用扩帧的技术,以下图所示,即每次输入除了当前帧外还须要输入当前帧的前几帧和后几帧。这是由于语音具备短时相关性,对输入几帧是为了更好的学习这种相关性学习
spectrum = magnitude * np.exp(1.0j * phase)
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)。在语音加强研究的发展过程当中,研究人员提出了一系列的时频掩蔽做为训练目标:
原理:因为语音在时频域上是稀疏分布的,对于一个具体的时频单元,语音和噪声的能量差别一般比较大,所以大多数时频单元上的信噪比极高或极低。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)$$
我看到过不少种写法
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
原理:基于语音和噪声正交,即不相关的假设下,即$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),它是均方偏差意义上的最优滤波器。
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
原理: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
原理: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数值分布直方图
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
参考文献: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$是纯净语音。
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后的相位信息要保存,用于还原语音信号。
基于掩蔽的语音加强和基于映射的语音加强模型训练和加强过程相似,这里只提几个重要的地方,其他地方参考上面内容。
enhance_magnitude = np.multiply(magnitude, mask)
首先看下实验效果,首先是基于映射语音加强的结果:
基于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
值得作一作的项目: