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

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

博客地址(转载请指明出处):https://www.cnblogs.com/LXP-Never/p/14142108.html
如果你觉得很不错,就像👍一样,关注就是对我最大的支持,谢谢😃

[En]

If you think it’s pretty good, like 👍, follow is the biggest support for me, thank you 😃

传统的语音增强方法都是基于一些先验假设,但这些先验假设是不合理的。此外,传统的语音增强依赖于参数设置、人工体验等。随着深度学习的发展,越来越多的人开始关注利用深度学习来解决语音增强问题。鉴于单声道的广泛应用,本文以单声道语音增强为例。

[En]

Traditional speech enhancement methods are based on some a priori assumptions, but these prior assumptions are unreasonable. In addition, traditional speech enhancement depends on the setting of parameters, manual experience and so on. With the development of deep learning, more and more people begin to pay attention to using deep learning to solve the problem of speech enhancement. As single-channel is widely used, this paper takes single-channel speech enhancement as an example.

目前基于DNN单通道大致可以分为两种方法,

  • 第一种寻求噪声语音谱与纯净语音谱的映射
  • *第二种基于mask的方法

基于映射的语音增强

基于映射的语音增强方法训练神经网络模型来映射噪声谱和纯谱之间的关系。该过程如下图所示。很多博客和文章也谈到了这种方法,但我觉得不详细,这让初学者看起来很困惑。

[En]

The mapping-based speech enhancement method trains the neural network model to map the relationship between the noise spectrum and the pure spectrum. The process is shown in the following figure. Many blogs and articles also talk about this method, but I think it is not detailed, which makes the beginners look confused.

训练阶段

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

  • Label:数据的 label为纯净语音信号的幅度谱,这里只需要一帧就够了。
  • 损失函数:学习噪声幅度谱与纯净语音信号的幅度谱类似于一个回归问题,因此损失函数采用回归常用的损失函数,如均方误差(MSE)、均方根误差(RMSE)或平均绝对值误差(MAE)等….

  • 最后一层的激活函数:由于是回归问题,最后一层采用线性激活函数

  • 其他:输入的幅度谱进行 归一化可以加速学习过程和更好的收敛。如果不采用幅度谱可以采用功率谱,要注意的是功率谱如果采用的单位是dB,需要对数据进行预处理,因为log的定义域不能为0,简单的方法就是在取对数前给等于0的功率谱加上一个非常小的数

增强阶段

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

基于Mask的语音增强

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

时频掩蔽

我们都知道,语音信号可以用时域波形或各种频谱来表示。这种外语语谱图可以同时在时域和频域显示信息,因此得到了广泛的应用,如下图所示。

[En]

We all know that speech signals can be represented by time domain waveforms or various frequency spectra. This foreign language spectrogram can display information in both time domain and frequency domain, so it is widely used, as shown in the following figure.

现在让我们假设有两个语音信号,一个是音乐信号,另一个是噪声信号,它们混合在一起,时域波形和对应的谱图如下图所示。

[En]

Now let’s assume that there are two speech signals, one is music signal, the other is noise, they are mixed together, and the time domain waveform and the corresponding spectrogram are shown in the following figure.

如果我们想要从混合信号中提取音乐信号(这一过程称为语音分离),在时域中很容易做到。现在我们从频域的角度来解决语音分离问题。首先,我们提出了两个假设:

[En]

If we want to extract the music signal from the mixed signal (a process called speech separation), it is easy to do in time domain. Now we solve the problem of speech separation from the perspective of frequency domain. First of all, we put forward two hypotheses:

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

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

基于上述两个假设,我们可以将期望信号从噪声信号中分离出来。为可能属于信号源的区域分配掩码1,为其余区域分配掩码0,如下图所示。

[En]

Based on the above two assumptions, we can separate the desired signal from the noise signal. Assign a mask of 1 to areas that may belong to a signal source, and assign a mask of 0 to the rest, as shown in the following figure.

我们通过0和1的二值掩码然后乘以混合信号的语谱图就可以得到我们想要喜好的语谱图了,如下图所示。

但是,这里存在一个问题,我们无法从语谱图中还原语音信号。为了解决这一问题,我们首先还原所有的频率分量,即对二值掩码做个镜像后拼接。假设我们计算语谱图时使用的是512点SFTF,我们一般去前257点进行分析和处理,在这里我们将前257点的后255做镜像,然后拼接在一起得到512点频率分量,如下图所示。

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

接下来介绍如何计算掩蔽值,掩蔽值计算方法有许多,但一般来说有两种常用的计算方法,分别为理想二值掩蔽(Ideal Binary Mask, IBM)和理想比值掩蔽(Ideal Ratio Mask, IRM)。IBM的计算公式如下:

$$公式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)$$

IRM为一个[0-1]的值,计算公式为:

$$公式3:\operatorname{IRM}(t, f)=\left(\frac{S^{2}(t, f)}{S^{2}(t, f)+N^{2}(t, f)}\right)^{\beta}=\left(\frac{\operatorname{SNR}(t, f)}{\operatorname{SNR}(t, f)+1}\right)^{\beta}$$

其中$\beta$为可调节尺度因子,一般取0.5。

基于mask方法的语音增强一般基于这种假设,在带噪语音谱中即存噪声信号又存在语音信号,因此将噪声信号掩蔽掉剩下的就是语音信号。目前常用的两种掩蔽方法:理想二值掩蔽、理想比值掩蔽

  • 理想二值掩蔽(Ideal Binary Mask,IBM),将分离任务变成了一个二分类问题。这类方法根据听觉感知特性,把音频信号分成不同的子带,根据每个时频单元上的信噪比,在噪声占主导情况下,把对应的时频单元的能量设为0,在目标语音占主导的情况下,保持原样。

  • 理想比值掩蔽(Ideal Ratio Mask,IRM),它同样对每个时频单元进行计算,但不同于IBM的”非零即一”,IRM中会计算语音信号和噪音之间的能量比,得到介于0到1之间的一个数,然后据此改变时频单元的能量大小。IRM是对IBM的演进,反映了各个时频单元上对噪声的抑制程度,可以进一步提高分离后语音的质量和可懂度。

基于掩蔽的语音增强和基于映射的语音增强模型的训练和增强过程是相似的,这里只提到几个要点,其余的参考上文。

[En]

Masking-based speech enhancement and mapping-based speech enhancement model training and enhancement process is similar, only a few important points are mentioned here, and the rest refer to the above.

  • Label:数据的label为根据信噪比计算的IBM或者IRM,这里只需要一帧就够了

  • 损失函数:IBM的损失函数可以用交叉熵,IRM的损失函数还是用均方差

  • 最后一层的激活函数:IBM只有0和1两个值,IRM范围为[0,1],因此采用sigmoid激活函数就可以了
  • 重构波形:首先用噪声幅度谱与计算的Mask值对应位置相乘,代码如下,然后根据相位信息重构语音波形。
enhance_magnitude = np.multiply(magnitude, mask)

Demo效果以及代码

首先,我们来看看实验结果,首先,基于映射语音增强的结果:

[En]

First of all, let’s take a look at the experimental results, first of all, based on the results of mapped speech enhancement:

基于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)</code">
</=>

参考文献

DNN单通道语音增强(附Demo代码)(大量抄袭于它)

基于Mask的语音分离

补充一下:https://blog.csdn.net/shichaog/article/details/105890125

Original: https://blog.csdn.net/qq_34218078/article/details/111307415
Author: 凌逆战
Title: 基于深度学习的单通道语音增强

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/524731/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球