语音信号处理之语音特征提取(1)机器学习的语音处理

本文首先是将Speech Processing for Machine Learning: Filter banks, Mel-Frequency Cepstral Coefficients (MFCCs) and What's In-Between这篇2016年4月21号的文章翻译了一下,然后讲出一点自己的理解和注释。

机器学习的语音处理:滤波器组,梅尔频率倒谱系数(MFCC)以及两者之间的区别

语音处理在任何语音系统中都扮演着重要的角色,无论它是自动语音识别(ASR)还是说话者识别等等。 长期以来,梅尔频率倒谱系数(MFCC)是非常受欢迎的功能。 但是最近,滤波器组正变得越来越流行。 在这篇文章中,我将讨论滤波器组和MFCC,以及为什么滤波器组变得越来越流行。

计算滤波器组和MFCC涉及相同的过程,在这两种情况下,都将计算滤波器组,并通过一些额外的步骤可以获得MFCC。 简而言之,信号会通过预加重滤波器。 然后将其切成(重叠的)帧,并将窗口函数应用于每个帧; 之后,我们在每个帧上进行傅立叶变换(或更具体地说是短时傅立叶变换),并计算功率谱; 然后计算滤波器组。 为了获得MFCC,可将离散余弦变换(DCT)应用于滤波器组,以保留多个所得系数,而其余系数则被丢弃。 两种情况的最后一步是平均归一化。

 

建立


       在本文中,我从此处使用了一个16位PCM wav文件,称为“ OSR_us_000_0010_8k.wav”,其采样频率为8000 Hz。 wav文件是干净的语音信号,包括单个语音,在其中说出一些句子,中间有一些停顿。 为简单起见,我使用了信号的前3.5秒,大致相当于wav文件中的第一句话。
       我将使用Python 2.7.x,NumPy和SciPy。 这篇文章中使用的一些代码基于该存储库中可用的代码。

import numpy
import scipy.io.wavfile
from scipy.fftpack import dct

sample_rate, signal = scipy.io.wavfile.read('OSR_us_000_0010_8k.wav') # File assumed to be in the same directory
signal = signal[0:int(3.5 * sample_rate)] #Keep the first 3.5 seconds

'''
显示这个图片
plt.figure(figsize=(20,5)) # 修改坐标显示
plt.xlabel("Time(s)")
plt.ylabel("Amplitude")
plt.plot(x , signal)
plt.show()
'''

原始信号在时域中具有以下形式:

时域信号

强化前

第一步是对信号应用预加重滤波器,以放大高频。 预加重滤波器在几种方面很有用:(1)平衡频谱,因为与低频相比,高频通常具有较小的幅度;(2)避免在傅立叶变换操作期间出现数值问题;(3)还可改善信号 噪声比(SNR)。

可以使用以下等式中的一阶滤波器将预加重滤波器应用于信号xx:

y(t)= x(t)-αx(t-1)y(t)= x(t)-αx(t-1)

可以使用以下代码轻松实现,其中滤波器系数(αα)的典型值为0.95或0.97,pre_emphasis = 0.97:

emphasized_signal = numpy.append(signal[0], signal[1:] - pre_emphasis * signal[:-1])

预加重在现代系统中的影响不大,主要是因为除避免了不应该成为问题的傅立叶变换数值问题外,大多数预加重滤波器的动机都可以使用均值归一化(在本文后面讨论)来实现。 在现代FFT实现中。
预加重后的信号在时域中具有以下形式:

预加重后的时域信号

构图


       经过预加重后,我们需要将信号分成短时帧。 此步骤的基本原理是信号中的频率会随时间变化,因此在大多数情况下,对整个信号进行傅立叶变换是没有意义的,因为我们会随时间丢失信号的频率轮廓。 为避免这种情况,我们可以安全地假设信号中的频率在很短的时间内是固定的。 因此,通过在此短时帧上进行傅立叶变换,我们可以通过串联相邻帧来获得信号频率轮廓的良好近似值。
       语音处理中的典型帧大小为20毫秒至40毫秒,连续帧之间有50%(+/- 10%)重叠。 流行的设置是帧大小为25毫秒,frame_size = 0.025和10毫秒跨度(重叠15毫秒),frame_stride = 0.01。

frame_length, frame_step = frame_size * sample_rate, frame_stride * sample_rate # Convert from seconds to samples
signal_length = len(emphasized_signal)
frame_length = int(round(frame_length))
frame_step = int(round(frame_step))
num_frames = int(numpy.ceil(float(numpy.abs(signal_length - frame_length)) / frame_step)) # Make sure that we have at least 1 frame

pad_signal_length = num_frames * frame_step + frame_length
z = numpy.zeros((pad_signal_length - signal_length))
pad_signal = numpy.append(emphasized_signal, z) # Pad Signal to make sure that all frames have equal number of samples without truncating any samples from the original signal

indices = numpy.tile(numpy.arange(0, frame_length), (num_frames, 1)) + numpy.tile(numpy.arange(0, num_frames * frame_step, frame_step), (frame_length, 1)).T
frames = pad_signal[indices.astype(numpy.int32, copy=False)]

窗口

将信号切成帧后,我们对每个帧应用诸如汉明窗之类的窗口函数。 Hamming窗口具有以下形式:

w [n] = 0.54-0.46cos(2πnN-1)w [n] = 0.54-0.46cos(2πnN-1)

其中,0≤n≤N-10≤n≤N-1,NN为窗口长度。 绘制先前的方程式将得出以下曲线:

Hamming Window

我们有几个原因需要将窗函数应用于这些帧,特别是要抵消FFT所做的数据无限的假设并减少频谱泄漏。

frames *= numpy.hamming(frame_length)
# frames *= 0.54 - 0.46 * numpy.cos((2 * numpy.pi * n) / (frame_length - 1)) # Explicit Implementation **

傅立叶变换和功率谱

现在,我们可以在每个帧上执行NN点FFT来计算频谱,这也称为短时傅立叶变换(STFT),其中NN通常为256或512,NFFT = 512; 然后使用以下公式计算功率谱(周期图):

P = | FFT(xi)| 2NP = | FFT(xi)| 2N

其中,xixi是信号xx的第i个帧。 这可以用以下几行实现:

mag_frames = numpy.absolute(numpy.fft.rfft(frames, NFFT)) # Magnitude of the FFT
pow_frames = ((1.0 / NFFT) * ((mag_frames) ** 2)) # Power Spectrum

滤波器

计算滤波器组的最后一步是将三角滤波器(通常为40个滤波器,在Mel等级上为nfilt = 40)应用于功率谱以提取频带。 梅尔音阶的目的是模仿低频的人耳对声音的感知,方法是在较低频率下更具判别力,而在较高频率下则具有较少判别力。 我们可以使用以下公式在赫兹(ff)和梅尔(mm)之间转换:

m = 2595log10(1 + f700)m =2595log10⁡(1 + f700)


f = 700(10m / 2595-1)f = 700(10m / 2595-1)

滤波器组中的每个滤波器都是三角形的,在中心频率处的响应为1,并朝着0线性减小,直到它到达两个相邻滤波器的中心频率处,响应为0,如下图所示:

梅尔秤上的滤波器组

可以通过以下方程式建模(从此处获取):

Hm(k)=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪0k-f( m-1)f(m)-f(m-1)1f(m + 1)-kf(m + 1)-f(m)0k <f(m-1)f(m-1)≤k< f(m)k = f(m)f(m)<k≤f(m + 1)k> f(m + 1)Hm(k)= {0k <f(m-1)k-f(m -1)f(m)-f(m-1)f(m-1)≤k<f(m)1k = f(m)f(m + 1)-kf(m + 1)-f(m )f(m)<k≤f(m + 1)0k> f(m + 1)

low_freq_mel = 0
high_freq_mel = (2595 * numpy.log10(1 + (sample_rate / 2) / 700)) # Convert Hz to Mel
mel_points = numpy.linspace(low_freq_mel, high_freq_mel, nfilt + 2) # Equally spaced in Mel scale
hz_points = (700 * (10**(mel_points / 2595) - 1)) # Convert Mel to Hz
bin = numpy.floor((NFFT + 1) * hz_points / sample_rate)

fbank = numpy.zeros((nfilt, int(numpy.floor(NFFT / 2 + 1))))
for m in range(1, nfilt + 1):
f_m_minus = int(bin[m - 1]) # left
f_m = int(bin[m]) # center
f_m_plus = int(bin[m + 1]) # right

for k in range(f_m_minus, f_m):
fbank[m - 1, k] = (k - bin[m - 1]) / (bin[m] - bin[m - 1])
for k in range(f_m, f_m_plus):
fbank[m - 1, k] = (bin[m + 1] - k) / (bin[m + 1] - bin[m])
filter_banks = numpy.dot(pow_frames, fbank.T)
filter_banks = numpy.where(filter_banks == 0, numpy.finfo(float).eps, filter_banks) # Numerical Stability
filter_banks = 20 * numpy.log10(filter_banks) # dB

将滤波器组应用于信号的功率谱(周期图)后,我们得到以下频谱图:

信号的频谱图

如果梅尔级滤波器组是所需的功能,那么我们可以跳过以归一化。

梅尔频率倒谱系数(MFCC)

       事实证明,在上一步中计算出的滤波器组系数是高度相关的,这在某些机器学习算法中可能会出现问题。 因此,我们可以应用离散余弦变换(DCT)去相关滤波器组系数,并生成滤波器组的压缩表示。 通常,对于自动语音识别(ASR),结果倒谱系数2-13将保留,其余的将被丢弃; num_ceps =12。丢弃其他系数的原因是,它们代表滤波器组系数的快速变化,而这些细微的细节对自动语音识别(ASR)毫无帮助。

mfcc = dct(filter_banks, type=2, axis=1, norm='ortho')[:, 1 : (num_ceps + 1)] # Keep 2-13

      可以将正弦提升器1应用于MFCC,以降低对较高MFCC的强调,这被认为可以改善噪声信号中的语音识别。

(nframes, ncoeff) = mfcc.shape
n = numpy.arange(ncoeff)
lift = 1 + (cep_lifter / 2) * numpy.sin(numpy.pi * n / cep_lifter)
mfcc *= lift #*

生成的MFCC:

MFCCs

平均归一化

如前所述,为了平衡频谱并改善信噪比(SNR),我们可以简单地从所有帧中减去每个系数的平均值。

filter_banks -= (numpy.mean(filter_banks, axis=0) + 1e-8)

均值归一化滤波器组:

归一化滤波器组

对于MFCC同样如此:

mfcc -= (numpy.mean(mfcc, axis=0) + 1e-8)

均值归一化MFCC:

标准化MFCC

滤波器组与MFCC


到目前为止,已根据其动机和实现方式讨论了计算滤波器组和MFCC的步骤。有趣的是,计算滤波器组所需的所有步骤都是由语音信号的性质和人类对此类信号的感知所激发的。相反,某些机器学习算法的局限性促使了计算MFCC所需的额外步骤。需要离散余弦变换(DCT)来使滤波器组系数去相关,该过程也称为白化。特别是,当高斯混合模型-隐马尔可夫模型(GMMs-HMM)非常流行并且MFCC和GMMs-HMM一起发展成为自动语音识别(ASR)2的标准方式时,MFCC便很受欢迎。随着语音系统中深度学习的出现,人们可能会质疑MFCC是否仍然是正确的选择,因为深度神经网络不太容易受到高度相关的输入的影响,因此离散余弦变换(DCT)不再是必要的步骤。值得注意的是,离散余弦变换(DCT)是线性变换,因此是不希望的,因为它会丢弃语音信号中的某些信息,这些信息是高度非线性的。
怀疑是否需要进行傅立叶变换是明智的。鉴于傅立叶变换本身也是线性运算,因此忽略它并尝试在时域中直接从信号中学习可能会有所帮助。确实,最近的一些工作已经尝试了这一点,并报告了积极的结果。但是,傅立叶变换操作是一个很难学习的操作,并且可以说会增加获得相同性能所需的数据量和模型复杂性。此外,在进行短时傅立叶变换(STFT)时,我们假设信号在此短时间内保持稳定,因此傅立叶变换的线性不会造成严重问题。

结论

在本文中,我们探讨了计算梅尔级滤波器组和梅尔频率倒谱系数(MFCC)的过程。 讨论了该过程中每个步骤的动机和实现。 我们也提出了与MFCC相比,滤波器组越来越受欢迎的原因。
tl; dr:如果机器学习算法不易受到高度相关的输入的影响,请使用梅尔级滤波器组。 如果机器学习算法易受相关输入的影响,请使用MFCC。

1.提升在倒频谱域中进行过滤。 请注意,频谱和倒谱中的符号分别使用了滤波和提升。 ↩
2.本文对此主题进行了精彩的讨论。 ↩

重要名词理解:

滤波器组:

人耳对声音频谱的响应是非线性的,经验表明:如果我们能够设计一种前端处理算法,以类似于人耳的方式对音频进行处理,可以提高语音识别的性能。FilterBank就是这样的一种算法。FBank特征提取要在预处理之后进行,这时语音已经分帧,我们需要逐帧提取FBank特征。

傅里叶变换:

人类听觉系统其实就像是在做傅里叶变换,通过耳蜗(可以视为一组滤波器)将时域声压转换为独立的不同频率的分量,但是,准确地说,更接近于短时傅里叶变换(STFT)。

傅里叶变换处理非平稳信号(频率随时间变化的信号)有天生缺陷。它只能获取一段信号总体上包含哪些频率的成分,但是对各成分出现的时刻并无所知。因此时域相差很大的两个信号,可能频谱图一样。
对于这样的非平稳信号,只知道包含哪些频率成分是不够的,我们还想知道各个成分出现的时间。知道信号频率随时间变化的情况,各个时刻的瞬时频率及其幅值——这也就是时频分析。
一个简单可行的方法就是——加窗。我又要套用方沁园同学的描述了,“把整个时域过程分解成无数个等长的小过程,每个小过程近似平稳,再傅里叶变换,就知道在哪个时间点上出现了什么频率了。”这就是短时傅里叶变换。

 梅尔频率倒谱系数(MFCC):

参考:https://zhuanlan.zhihu.com/p/27416870

均值归一化:

参考:https://www.jianshu.com/p/ef3534ddda15

第一部分构图用的完整代码:

import numpy
import scipy.io.wavfile
from scipy.fftpack import dct
import matplotlib as mpl
import matplotlib.pyplot as plt

sample_rate, signal = scipy.io.wavfile.read('OSR_us_000_0010_8k.wav') # File assumed to be in the same directory
signal = signal[0:int(3.5 * sample_rate)] #Keep the first 3.5 seconds


plt.figure(figsize=(20,5)) # 修改坐标显示
plt.xlabel("Time(s)")
plt.ylabel("Amplitude")
plt.plot(signal)
plt.show()

参考文章:https://www.jianshu.com/p/b416d5617b0c

猜你喜欢

转载自blog.csdn.net/notlikeregist/article/details/106923641