출처: <http://haythamfayek.com/2016/04/21/speech-processing-for-machine-learning.html>
이 글에서는 필터 뱅크와 MFCC에 대해 논의하고 필터 뱅크가 인기를 얻는 이유에 대해 설명한다.
필터뱅크를 계산하는 것과 MFCC를 계산하는 것은 어느 정도 같은 과정을 포함한다. 두 경우 모두 필터뱅크는 계산하여야 하고, 약간의 추가적인 스탭을 거쳐 MFCC를 얻을 수 있다.
간단히 말하면,
- pre-emphasis filtering: s1 = pre-emphasis filter(s)
- Framing and windowing: s12 = framing(s1), s2 = windowing(s21)
- Fourier transform and Power spectrum: s3 = fft(s2), s4 = p_spec(s3)
- Computation of the filterbank: s5 = cal_filterbank(s4)
- Discrete Cosine Transform: s6 = dct(s5)
- mean normalization:
Filterbank = mean_norm(s5)
MFCC = mean_norm(s6)
1. pre-emphasis filtering
고주파를 증폭킴으로써 얻을 수 있는 장점:
- 고주파 성분은 저주파 성분에 비해 크기가 작기 때문에 이를 통해 주파수 스팩트럼의 밸러스를 맞출 수 있다.
- 푸리에 변환 중 발생할 수 있는 수치 문제를 피할 수 있다.
- SNR을 개선할 수 있다.
필터 식 : 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])
- 요즘은 음성 신호처리에서 pre_emphasis filtering을 하지 않는다. 왜냐하면 이 후에 적용될 cepstral mean normalization과 같은 단계에서 이를 보상할 수 있기 때문이다. pre_emphasis filtering은 과거 구형 시스템의 산물일 뿐이다.
출처: <https://www.quora.com/Why-is-pre-emphasis-i-e-passing-the-speech-signal-through-a-first-order-high-pass-filter-required-in-speech-processing-and-how-does-it-work/answer/Nickolay-Shmyrev?srid=e4nz&share=71ca3e28>
2. Framing and Windowing
음성 신호는 nonstationary하므로 이를 한꺼번에 푸리에 변환하는 것은 의미가 없다. 일반적으로 음성 처리에서,
- 프레임 길이 : 20ms~40ms
- 오버랩 : 50% (+/- 10%)
- 해밍윈도우
파이썬 코드:
frame_size = 0.025 # 25ms
frame_stride = 0.01 # 10ms hop-size == 15ms overlap
# Convert from seconds to samples.
frame_length, frame_step = frame_size * sample_rate, frame_stride * sample_rate
signal_length = len(emphasized_signal)
frame_length = int(round(frame_length))
frame_step = int(round(frame_step))
# Make sure that we have at least 1 frame.
num_frames = int(numpy.ceil(float(numpy.abs(signal_length - frame_length)) /
frame_step))
pad_signal_length = num_frames * frame_step + frame_length
z = numpy.zeros((pad_signal_length - signal_length))
# Pad Signal to make sure that all frames have equal number of samples without
# truncating any samples from the original signal.
pad_signal = numpy.append(emphasized_signal, z)
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 window
frames *= numpy.hamming(frame_length)
# Explicit Implementation **
# frames *= 0.54 - 0.46 * numpy.cos((2 * numpy.pi * n) / (frame_length - 1))
3. Fourier transform and Power spectrum
N-point FFT을 적용. 일반적으로 N = 256 또는 512를 사용
Power spectrum (periodogram) = |FFT(x_i)|^2 / N
파이썬 코드:
NFFT = 512
NFFT = 512
mag_frames = numpy.absolute(numpy.fft.rfft(frames, NFFT)) # Magnitude of the FFT
pow_frames = ((1.0 / NFFT) * ((mag_frames) ** 2)) # Power Spectrum
4. Filterbanks
Mel 스케일의 Trianglar filter (일반적으로 40 filters)를 Power spectrum에 적용
![]() |
Filterbanks on the Mel-scale |
Mel과 Hz의 관계식:
f = 700(10^(m∕2595)−1)
m = 2595 log_10(1+f/700)
Filterbank 식:
![]() |
Filterbanks equation |
파이썬 코드:
low_freq_mel = 0
nfilt = 22
low_freq_mel = 0
nfilt = 22
# Convert Hz to Mel
high_freq_mel = (2595 * numpy.log10(1 + (sample_rate / 2) / 700))
# Equally spaced in Mel scale
mel_points = numpy.linspace(low_freq_mel, high_freq_mel, nfilt + 2)
# Convert Mel to Hz
hz_points = (700 * (10**(mel_points / 2595) - 1))
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)
# Numerical Stability
filter_banks = numpy.where(filter_banks == 0, numpy.finfo(float).eps, filter_banks)
filter_banks = 20 * numpy.log10(filter_banks) # dB
5. Mel-frequency Cepstral Coefficients (MFCCs)
이전 단계에서 계산한 필터 뱅크 계수는 상관도가 매우 높기때문에 머신러닝 알고리듬에서 문제가 될 수도 있다고 알려져있다. 따라서 필터 뱅크 계수의 상관도를 줄이기위해 DCT를 적용한다. 그러면 압축된 필터뱅크의 형태를 얻을 수 있다. 일반적으로 음성 인식에서는 2-13개만 남기고 나머지는 버린다. 이유는 나머지의 계수가 음성 인식에 크게 기여하지 못하기 때문이다.
파이썬 코드:
num_ceps = 12
# Keep 2-13
mfcc = dct(filter_banks, type=2, axis=1, norm='ortho')[:, 1 : (num_ceps + 1)]
(nframes, ncoeff) = mfcc.shape
n = numpy.arange(ncoeff)
lift = 1 + (cep_lifter / 2) * numpy.sin(numpy.pi * n / cep_lifter)
mfcc *= lift #*
6. Mean Normalization
스팩트럼의 밸런스를 유지하고 SNR을 향상시키기 위한 것으로, 모든 프레임에서 각 계수의 평균을 빼기만하면 된다.
파이썬 코드:
filter_banks -= (numpy.mean(filter_banks, axis=0) + 1e-8)
mfcc -= (numpy.mean(mfcc, axis=0) + 1e-8)
Filterbanks vs. MFCC
필터뱅크를 계산하는 모든 과정은 음성신호와 인간의 인지에 대한 본성을 동기로 삼는다.
MFCC를 위한 추가적인 계산 과정은 머신러닝 알고리즘의 한계가 동기가 되었다.
MFCC에서 DCT는 필터뱅크 계수간의 상관도를 낮추는 과정으로 화이트닝(whitening)으로 간주된다. GMM-HMM이 유행할 때 MFCC도 유행하였다.
특히 MFCC와 GMMs-HMMs이 공동으로 자동음성인식의 표준기법 발전한 경우 매우 인기가 있었다.
음성인식 시스템에 딥러닝을 적용하는 요즘에는 상관도가 높은 입력에 덜 민감한 딥뉴럴네트워크에 과연 MFCC가 옳은 선택인가를 생각해볼 필요가 있다. 선형 변환인 DCT로 인해 비선형성이 강한 음성의 일부 정보가 사라지는 것은 바라는 바가 아니다.
푸리에 변환도 과연 필요한가?라는 질문도 할 수 있다. 푸리에 변환도 선형 변환이기 때문에 이를 무시하고 시간 영역의 신호를 직접 학습하는 것도 좋을 것이다. 사실 이미 이에 대한 긍정적인 연구 결과가 발표되었다.
결론
머신러닝 알고리즘이 상관도가 높은 입력에 영향을 받지 않는다면 Mel-scaled 필터뱅크를 사용하고,
머신러닝 알고리즘이 상관도가 높은 입력에 영향을 받기 쉬운 환경이라면 MFCC를 사용하라.
많은 도움이 되었습니다. 그런데 상관도가 높은 입력이라는게 무슨의미인지 감이 잘 안와서, 혹시 간단하게 설명해주시거나 구글링할만한 키워드를 알려주신다면 정말 감사하겠습니다
답글삭제