푸리에 변환(Fourier Transform): 시간(time)에 대한 함수(혹은 신호)와 주파수(frequency)에 대한 함수(혹은 신호)를 잇는 수학적인 연산
두 개의 단순파(simple wave) -> 시간 도메인에서 복합파로 표현
푸리에 변환 실시
주파수 도메인에서 두 개의 주파수 성분으로 분해 가능
음성 인식 -> 대개 이산 푸리에 변환사용
이산푸리에 변환 = 특정 도메인의 이산신호를 다른 도메인의 이산신호로 변환하는 과정
이산푸리에 변환
역푸리에 변환: 이산 푸리에 변환을 실시한 뒤 이를 다시 원래 도메인으로 변환하는 과정
이산 역푸리에 변환: 역푸리에 변환을 이산 신호에 대해서 실시한 것
(exp 지수 부분에 음수 유무 차이)
오일러 공식
복소 지수 함수 (cis): 오일러 공식에서 바로 유도 가능
첫쨰줄
둘째줄
푸리에 변환 결과는 복소수 형태
exp 앞에 시간 도메인의 원래 시그널 x_n이 곱해짐
지수를 포함한 exp 항: 복소평면 단위원 위에서 이뤄지는 운동
복소평면상 이들 점들을 모두 더하는 것 = 이산 푸리에 변환을 적용한 X_k
이산 푸리에 변환을 matrix form으로
벡터 x: 시간 도메인의 원래 신호
행렬 W: 수식1 exp 항에 대응 (N * N), 행 인덱스: k, 열 인덱스:n
행렬 W = DFT 행렬
import numpy as np
def DFT(x):
x = np.asarray(x, dtype=float)
N = x.shape[0]
n = np.arange(N)
k = n.reshape((N, 1))
W = np.exp(-2j * np.pi * k * n / N)
return np.dot(W, x)
x = np.random.random(1024)
np.allclose(DFT(x), np.fft.fft(x))
음성 인식과 관련해 불필요한 정보는 버리고 중요한 특질만 남긴 피처
입력 음성을 짧은 구간으로 분할
프레임 각각에 푸리에 변환 실시 => 해당 구간 음성에 담긴 주파수 정보를 추출
스펙트럼: 모든 프레임 각각에 푸리에 변환 실시 결과
필터: 스펙트럼에 말소리에 민감한 주파수는 세밀하게, 나머지는 덜 보게 분석하기 위해 쓰임 => 멜 스펙트럼
로그 멜 스펙트럼: 멜 스펙트럼에 로그 취함
MFCC: 로그 멜 스펙트럼에 역푸리에 변환을 해서, 주파수 도메인의 정보를 새로운 시간 도메인으로 바꾼 것
MFCC: 말 소리 인식에 중요한 특징들 추출한 결과 -> 도메인 지식 활용해 전문가가 공식화한 것 !!
MFCC 어떻게 만들까???
import scipy.io.wavfile
sample_rate, signal = scipy.io.wavfile.read('example.wav')
len(signal) / sample_rate
signal = signal[0:int(3.5 * sample_rate)]
사람 말소리를 스펙트럼으로 변환해서 관찰하면
다음 세 효과
t번째 시점의 원시 음성 신호를 xt => 게산 식
coefficient α -> 보통 0.95나 0.97을 사용
코드
import numpy as np
pre_emphasis = 0.97
emphasized_signal = np.append(signal[0], signal[1:] - pre_emphasis * signal[:-1])
frame_size = 0.025
frame_stride = 0.01
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))
num_frames = int(np.ceil(float(np.abs(signal_length - frame_length)) / frame_step))
pad_signal_length = num_frames * frame_step + frame_length
z = np.zeros((pad_signal_length - signal_length))
pad_signal = np.append(emphasized_signal, z)
indices = np.tile(np.arange(0, frame_length), (num_frames, 1)) + \
np.tile(np.arange(0, num_frames * frame_step, frame_step), (frame_length, 1)).T
frames = pad_signal[indices.astype(np.int32, copy=False)]
정의: 각각의 프레임에 특정 함수를 적용해 경계를 스무딩하는 기법
대표 예시: 해밍 윈도우 함수
왜? 이렇게 할까?
문제점 => Rectangular Window로 자른 프레임의 양끝에서는 신호가 살아 있다가 갑자기 죽는(=0) 상황이 발생 ~> 프레임에 이후 푸리에 변환을 실시하게 되면 불필요한 고주파(high frequency) 성분이 살아남게 됨
Hamming window 적용한 경우, 양 끝이 스무딩 되어서 -> 부작용 완화가 가능해짐
정의: 시간(time) 도메인의 음성 신호를 주파수(frequency) 도메인으로 바꾸는 것
이산 푸리에 변환: 이산 신호(discrete signal)에 대한 푸리에 변환
실제 적용할 때 => 고속 푸리에 변환 사용 (기존 방법에서 중복 계산량 줄이는 방법)
파이썬 -> np.fft.fft 함수
NFFT = 512
dft_frames = np.fft.rfft(frames, NFFT)