Audio Task
화자 인식
voice agent wake
음성을 input으로 받아 디바이스 제어
명령을 실행한 결과 음성 합성을 통해 출력
Task
Sound
Speech Classification
Auto-tagging
Speech
Speech-to-Text(음성 인식; STT) = ASR(Automatic Speech Recognition)
특정 키워드를 찾거나 화자를 인식할 때 사용
STT의 output을 NLP task의 input으로 연결시키기도 함
Text-to-Speech(음성 합성; TTS)
텍스트를 입력으로 받아 사람의 음성처럼 합성해주는 task
text → \rarr → spectrogram: acoustic model
text → \rarr → speech: vocoder
Speech-to-Speech(음성 변환; SPS)
Digital Signal
Sound
소리의 물리량
소리는 진동으로 인한 공기의 압축으로 생성
그 압축이 얼마나 되었느냐에 따라서 Wave(파동)으로 표현
파동: 진동하며 공간/매질을 전파해 나가는 현상
질량의 이동은 없지만 에너지/운동량의 운반은 존재
파동에서는 진폭(Amplitude ), 주파수(Frequency ), 위상(Phase ) 얻을 수 있음
진폭: 파동의 진동 폭(최대치 - 최저치)
파동 방정식: x = A \sin{(t-k)}+b
주파수: 주기적인 현상이 단위 시간 동안 몇 번 일어났는 지를 의미
Hz(헤르츠), rpm, BPM 등 단위 사용
파장과 역수 관계
위상: 반복되는 파형의 한 주기에서 첫 시작점의 각도 혹은 어느 한 순간의 위치
파장(Wave Length): 파동의 한 번의 주기가 갖는 길이
심리 음향
소리의 물리적 특성이 동일하더라도 종종 청자에 따라 다르게 인식
소리라는 물리적 현상을 인간은 주관적이고 감각적으로 인식하는 것
복잡파(Complex Wave)
우리가 사용하는 대부분의 소리는 복합파
복수의 서로 다른 정현파들의 합으로 이루어진 파형
정현파: 일종의 복소 주기 함수(복소수가 있는 주기 함수)
Sinusoid
정현파(Sinusoid)는 주기 신호를 총칭
오일러 공식: e i θ = cos θ + i sin θ e^{i \theta} = \cos{\theta} + i \sin{\theta} e i θ = cos θ + i sin θ
세계에서 가장 아름다운 공식
복소수 지수를 정의하는 데에 출발점이 되며, 삼각 함수와 지수 함수에 대한 관계를 나타냄
θ \theta θ : 실수
θ = π \theta = \pi θ = π 를 대입해 오일러 등식 구함
오일러 등식: e i π + 1 = 0 e^{i \pi} + 1 = 0 e i π + 1 = 0
삼각 함수에 의한 표현: x ( t ) = A cos ( w 0 t + Φ ) x(t) = A \cos{(w_0 t + \Phi)} x ( t ) = A cos ( w 0 t + Φ )
복소지수(복소수 지수)에 의한 표현: x ( t ) = R e { A ⋅ e i ( w 0 t + Φ ) } = A ⋅ e i ( w 0 t + Φ ) + A ⋅ e − i ( w 0 t + Φ ) 2 x(t) = Re\{A \cdot e^{i(w_0 t + \Phi)}\} = \cfrac{A \cdot e^{i(w_0 t + \Phi)} + A \cdot e^{-i(w_0t + \Phi)} }{2} x ( t ) = R e { A ⋅ e i ( w 0 t + Φ ) } = 2 A ⋅ e i ( w 0 t + Φ ) + A ⋅ e − i ( w 0 t + Φ )
A A A : 진폭, w 0 w_0 w 0 : 주파(= 2 ϕ f = 2 \phi f = 2 ϕ f ), Φ \Phi Φ : 위상
import librosa. display
def sinusoid ( t, f, phi = 0 ) :
return np. cos( 2 * np. pi* f* t + phi)
def complex_sinusoid ( t, f, phi = 0 ) :
cSin = np. exp( 1j * ( 2 * np. pi* f* t + phi) ) + np. exp( - 1j * ( 2 * np. pi* f* t + phi ) )
return cSin/ 2
def uniform_samples ( sr, dur) :
return np. arange( sr* dur) / sr
def sinusoid_samples ( sr, f = 8000 , dur = 1 , isComplex = False ) :
ts = uniform_samples( sr, dur)
if isComplex:
sin = complex_sinusoid( ts, f)
else :
sin = sinusoid( ts, f)
return ts, sin
sr = 48000
fig, ax = plt. subplots( 2 , sharex = 'all' )
ts, sin = sinusoid_samples( sr, isComplex = False )
ax[ 0 ] . plot( ts[ : 100 ] , sin[ : 100 ] )
ax[ 0 ] . set_title( "Real Sinusoids" )
cts, csin = sinusoid_samples( sr, isComplex = True )
ax[ 1 ] . plot( cts[ : 100 ] , csin[ : 100 ] )
ax[ 1 ] . set_title( "Complex Sinusoids" )
ax[ 1 ] . set_xlabel( "time (sec)" )
fig. tight_layout( )
plt. show( )
Sampling & Quantization
소리는 연속적인 데이터.
컴퓨터에 저장하기 위해서는 Sampling과 Quantization을 통해 discrete하게 표현해야 한다.
우선 실수 형태인 시간을 저장하기 위해 sampling 진행
Sampling과 Quantization 조절을 통해 가벼운 모델 생성 가능
Quantization은 보통 안하지만, 경량화를 위해 하기도 함.
Sampling
시간을 이산적인 구간으로 나누는 것
샘플링 간격에 따라 amplitude를 측정
1초의 연속적인 신호를 몇 개의 숫자들의 sequence로 표현할 것인가: sampling rate f s f_s f s !!
sampling rate이 클수록 원본 데이터와 비슷
sampling rate이 작을 수록 원본 데이터로 복원이 어려움(원래 신호보다 주파수가 낮은 형태로 나옴)
Nyquist 이론에 의해 sampling rate이 원래 신호에 존재하는 주파수의 최대값의 2배 보다 크면 원래 신호를 손실 없이 복원 가능
일반적으로 Audio CD는 44.1kHz, Speech는 8kHz의 sampling rate 사용
import IPython. display as ipd
wav, sr = librosa. load( librosa. ex( 'brahms' ) , duration= 5 )
for new_sr in [ sr, 16000 , 8000 , 4000 ] :
new_wav = librosa. resample( wav, sr, new_sr)
print ( "Sampling rate:" , new_sr)
ipd. display( ipd. Audio( new_wav, rate= new_sr) )
Quantization
파장을 이산적인 구간으로 나누고, 신호 데이터의 파장을 반올림해 저장
보통 bit로 나타냄
B bit의 Quantization: − 2 B − 1 -2^{B-1} − 2 B − 1 ~ $ 2^{B-1}-1$
Audio CD의 Quantization(16 bits): − 2 15 -2^{15} − 2 1 5 ~2 15 − 1 2^{15}-1 2 1 5 − 1
해당 값들은 보통 − 1.0 -1.0 − 1 . 0 ~ 1.0 1.0 1 . 0 영역으로 scaling하기도 함
Quantization을 거치고 나면 기계음이 섞임
wav, sr = librosa. load( librosa. ex( 'brahms' ) , duration = 5 )
normed_wav = wav / max ( np. abs ( wav) )
print ( "Scaled to [-1., 1.]" )
ipd. display( ipd. Audio( normed_wav, rate= sr) )
Bit = 4
max_value = 2 ** ( Bit- 1 )
quantized_8_wav = normed_wav * max_value
quantized_8_wav = np. round ( quantized_8_wav) . astype( int )
quantized_8_wav = np. clip( quantized_8_wav, - max_value, max_value- 1 )
print ( Bit, "bit Quantization" )
ipd. display( ipd. Audio( quantized_8_wav, rate= sr) )
임의의 입력 신호를 다양한 주파수를 갖는 주기 함수 들의 합으로 분해 해 표현
각 주기 함수들의 진폭을 구하는 과정
푸리에는 임의의 함수를 삼각 함수의 무한 선형 조합으로 표현할 수 있다고 주장하였다.
힐베르트 공간 F = L 2 ( [ 0 , 2 π ] ) F = L^2([0, 2\pi]) F = L 2 ( [ 0 , 2 π ] ) 상에서 지수함수계 E = 1 2 π e j n t : n = 0 , 1 , ± 1 , ± 2 , … E = {\cfrac{1}{\sqrt{2\pi}}e^{jnt}: n=0, 1, \pm1, \pm2, \dots} E = 2 π 1 e j n t : n = 0 , 1 , ± 1 , ± 2 , … 는 완비정규직교 기저 집합이므로 [ 0 , 2 π ] [0,2\pi] [ 0 , 2 π ] 에서 정의되는 주기함수들은 다음과 같이 푸리에 급수로 표현
힐베르트 공간: 모든 코시 열(점 사이의 거리가 점점 가까워지는 수열)의 극한이 존재하는 내적 공간
완비정규직교 기저 집합
기저: 벡터 공간을 만들어 낼 수 있는 가장 작은 것
x ( t ) = Σ n = − ∞ ∞ a n 1 2 π e j n t a n = < f , p n > = ∫ 0 2 π x ( t ) 1 2 π e − j n t d t x(t) = \Sigma^{\infin}_{n=-\infin} a_n \cfrac{1}{\sqrt{2\pi}}e^{jnt}\\ a_n = <f, p_n> = \int^{2\pi}_{0} {x(t) \cfrac{1}{\sqrt{2\pi}}e^{-jnt}} dt x ( t ) = Σ n = − ∞ ∞ a n 2 π 1 e j n t a n = < f , p n > = ∫ 0 2 π x ( t ) 2 π 1 e − j n t d t
첫 번째 식에서 n n n 은 − ∞ -\infin − ∞ ~ ∞ \infin ∞ 의 범위를 갖고 움직임
어떠한 신호가 서로 다른 주기 함수들의 합으로 표현되는데, 그 주기 함수는 무한개가 있어야 함
지수함수계 함수들의 모두 직교
x ( t ) x(t) x ( t ) 는 기저를 E E E 로 두면, ( a − ∞ , … , a ∞ ) (a_{-\infin}, \dots, a_{\infin}) ( a − ∞ , … , a ∞ ) 라는 좌표로 표현 가능
주기가 무한대인 일반적인 함수로 일반화하면, Fourier Transform 식을 얻을 수 있다.
I n v e r s e _ F o u r i e r _ T r a n s f o r m : x ( t ) = 1 2 π ∫ − ∞ ∞ X ( j w ) e j w t d w F o u r i e r _ T r a n s f o r m : X ( j w ) = ∫ − ∞ ∞ x ( t ) e − j w t d t Inverse\_Fourier\_Transform: x(t) = \cfrac{1}{2\pi} \int^{\infin}_{-\infin}X(jw)e^{jwt} dw\\ Fourier\_Transform: X(jw) = \int^{\infin}_{-\infin} x(t) e^{-jwt} dt I n v e r s e _ F o u r i e r _ T r a n s f o r m : x ( t ) = 2 π 1 ∫ − ∞ ∞ X ( j w ) e j w t d w F o u r i e r _ T r a n s f o r m : X ( j w ) = ∫ − ∞ ∞ x ( t ) e − j w t d t
time domain에 연속적이고 무한한 길이의 신호를 frequency domain에서 연속적이고 무한한 주파수로 나타낼 수 있다.
우리가 sampling한 신호는 시간의 간격과 소리의 진폭이 모두 discrete한 데이터
푸리에 변환 식을 Discrete하게 바꿔야 함
I n v e r s e _ D i s c r e t e _ F o u r i e r _ T r a n s f o r m : x [ t ] = 1 N Σ k = 0 N − 1 X [ k ] e j w k n D i s c r e t e _ F o u r i e r _ T r a n s f o r m : X [ k ] = Σ n = 0 N − 1 x [ n ] e − j w k n Inverse\_Discrete\_Fourier\_Transform: x[t] = \cfrac{1}{N} \Sigma^{N-1}_{k=0} X[k]e^{jw_kn}\\ Discrete\_Fourier\_Transform: X[k] = \Sigma^{N-1}_{n=0} x[n]e^{-jw_kn} I n v e r s e _ D i s c r e t e _ F o u r i e r _ T r a n s f o r m : x [ t ] = N 1 Σ k = 0 N − 1 X [ k ] e j w k n D i s c r e t e _ F o u r i e r _ T r a n s f o r m : X [ k ] = Σ n = 0 N − 1 x [ n ] e − j w k n
우리가 가진 신호 x [ t ] x[t] x [ t ] 에서 이산 시계열 데이터가 주기 N으로 반복된다고 할 때, DFT는 주파수와 진폭이 서로 다른 N개의 사인 함수의 합으로 표현
스펙트럼 X [ k ] X[k] X [ k ] 를 원래 시계열 데이터에 대한 퓨리에 변환값이라고 함
Zero Padding
신호의 길이가 잛으면 DFT된 신호가 정확히 표현되지 않는다.
x ( n ) x(n) x ( n ) 뒤에 0을 여러개 덧붙여서 강제로 길이 늘림
신호의 뒤에 0을 붙여 DFT를 수행하면, 샘플 수가 늘어난 X ( m ) X(m) X ( m ) 을 얻음
고밀도의 스펙트럼 얻을 수 있음
신호의 샘플 수가 늘어나게 되어 X ( m ) X(m) X ( m ) 이 더욱 촘촘한 간격의 한 주기 신호로 생성
단순히 0을 덧붙여 특성이나 분해능에는 영향 없음
분해능: 서로 떨어져 있는 두 물체를 서로 구별할 수 있는 능력
즉, 고밀도의 스펙트럼을 얻을 수는 있지만 고분해능의 스펙트럼을 얻을 수 있는 것은 아님
def DFT ( x) :
N = len ( x)
X = np. array( [ ] )
nv = np. arange( N)
for k in range ( N) :
s = np. exp( 1j * 2 * np. pi* k / N* nv)
X = np. append( X, sum ( x* np. conjugate( s) ) )
return X
def plot_magnitude ( DFT, ax, N) :
magnitude = np. abs ( DFT)
freq_axis = 2 * np. pi * np. arange( magnitude. shape[ - 1 ] ) / N
ax. stem( freq_axis, magnitude, 'k' , markerfmt= 'ko' , basefmt= " " , use_line_collection= True )
ax. set ( title= "Magnitude of DFT" , xlim= ( 0 , np. pi) , xticks= [ 0 , np. pi/ 2 , np. pi] , xticklabels= [ r'$0$' , r'$\pi/2$' , r'$\pi$' ] , xlabel = 'Frequency (rad)' ,
ylabel= 'Phase (rad)' , ylim= ( - np. pi, np. pi) , yticks= [ - np. pi, 0 , np. pi] , yticklabels= [ r'$-\pi$' , r'$0$' , r'$\pi$' ] )
def plot_phase ( DFT, ax, N) :
magnitude = np. abs ( DFT)
phase = np. angle( DFT)
freq_axis = 2 * np. pi * np. arange( magnitude. shape[ - 1 ] ) / N
ax. stem( freq_axis, phase, 'k' , markerfmt = 'ko' , basefmt = " " , use_line_collection = True )
ax. set ( title= "Phase of DFT" , xlim = ( 0 , np. pi) , xticks= [ 0 , np. pi / 2 , np. pi] , xticklabels= [ r'$0$' , r'$\pi/2$' , r'$\pi$' ] , xlabel = 'Frequency (rad)' ,
ylabel= 'Phase (rad)' , ylim= ( - np. pi, np. pi) , yticks= [ - np. pi, 0 , np. pi] , yticklabels= [ r'$-\pi$' , r'$0$' , r'$\pi$' ] )
num_samples = 47
sinusoid = np. cos( np. pi * 2 / 5.5 * np. arange( num_samples) )
X = DFT( sinusoid)
fig, axes = plt. subplots( ncols= 3 )
axes[ 0 ] . stem( np. arange( num_samples) , sinusoid, 'k' , markerfmt = 'ko' , basefmt = " " , use_line_collection = True , )
axes[ 0 ] . set ( title= "Discrete signal" , xlabel= "Discrete index" , ylabel= "Amplitude" )
plot_magnitude( X, axes[ 1 ] , sinusoid. shape[ - 1 ] )
plot_phase( X, axes[ 2 ] , sinusoid. shape[ - 1 ] )
fig. set_size_inches( 18 , 6 )
fig. tight_layout( )
plt. show( )
spectrogram vs spectrum
wav, sr = librosa. load( librosa. ex( "brahms" ) , duration = 3 )
ipd. display( ipd. Audio( wav, rate= sr) )
S = librosa. core. stft( wav, n_fft= 1024 , hop_length= 512 , win_length= 1024 )
log_S = librosa. power_to_db( np. abs ( S) ** 2 , ref= np. max )
fig, axes = plt. subplots( ncols= 2 )
plot_magnitude( np. fft. rfft( wav) , axes[ 0 ] , wav. shape[ - 1 ] )
librosa. display. specshow( log_S, sr= sr, x_axis= 'time' , y_axis= 'hz' , ax = axes[ 1 ] )
axes[ 1 ] . set_title( "STFT" )
fig. set_size_inches( 12 , 4 )
fig. tight_layout( )
plt. show( )
spectrum과 spectrogram의 가장 큰 차이는 시간 축의 유무
spectrum은 시간 축이 없어 특정 시간대의 snapshot을 찍어 소리의 에너지를 분석한 것
spectrogram은 시간에 따른 소리의 변화를 시각화한 것
대부분의 신호는 시간에 따라 주파수가 변하게 됨
FFT는 어느 시간대에 주파수가 변하는지 알 수 없음
STFT는 시간을 프레임 별로 나눠 FFT 수행
STFT
X ( l , k ) = Σ n = 0 N − 1 w ( n ) x ( n + l H ) e − 2 π k n N X(l,k) = \Sigma^{N-1}_{n=0} w(n)x(n+lH)e^{\cfrac{-2\pi kn}{N}} X ( l , k ) = Σ n = 0 N − 1 w ( n ) x ( n + l H ) e N − 2 π k n
주파수의 특성이 시간에 따라 달라지는 사운드를 분석하는 방법
시계열 데이터를 일정한 시간 구간(window-size)로 나누고, 각 구간에 대해 스펙트럼을 구함
(time, frequency, magnitude) 꼴로 spectrogram이 나옴
시간을 x축, 주파수를 y축, 크기를 색으로 표시
파라미터
N N N : FFT size
window를 얼마나 많은 주파수 밴드로 나누는가
w ( n ) w(n) w ( n ) : windown function
n n n : window size
window함수에 들어가는 sample의 양
작을수록 low-frequency resolution을 갖고, high-time resolution을 가짐
길수록 high-frequency resolution, low-time resolution을 가짐
H H H : hop size
윈도우가 겹치는 사이즈
일반적으로 1/2, 1/4 정도 겹치게 함
Convolution Theorem
( x ∗ y ) [ n ] : = Σ m = − ∞ ∞ x m ] y [ n − m ] ( x ∗ y ) ( t ) : = ∫ − ∞ ∞ x ( γ ) y ( t − γ ) d γ (x*y)[n] := \Sigma^{\infin}_{m=-\infin} xm]y[n-m]\\ (x*y)(t) := \int^{\infin}_{-\infin} x(\gamma)y(t-\gamma)d\gamma ( x ∗ y ) [ n ] : = Σ m = − ∞ ∞ x m ] y [ n − m ] ( x ∗ y ) ( t ) : = ∫ − ∞ ∞ x ( γ ) y ( t − γ ) d γ
( x ⊗ y ) [ n ] : = Σ m = 0 N − 1 x [ m ] y [ ( n − m ) N ] ( x ⊗ y ) ( t ) : = ∫ − ∞ ∞ x ( γ ) y ( ( t − γ ) γ ) d γ (x\otimes y)[n] := \Sigma^{N-1}_{m=0} x[m]y[(n-m)_N]\\ (x\otimes y)(t) := \int^{\infin}_{-\infin} x(\gamma)y((t-\gamma)_\gamma)d\gamma ( x ⊗ y ) [ n ] : = Σ m = 0 N − 1 x [ m ] y [ ( n − m ) N ] ( x ⊗ y ) ( t ) : = ∫ − ∞ ∞ x ( γ ) y ( ( t − γ ) γ ) d γ
한 도메인에서의 convolution은 다른 도메인에서의 element-wise multiplicationdlek.
주파수 도메인에서의 연산과 시간 도메인에서의 연산을 자유자재로 변환 가능
Window
STFT는 신호를 frame 별로 잘라서 FFT 수행
window는 frame을 어떻게 자를 것인지 결정
num_samples = 47
sinusoid = np. cos( np. pi * 2 / 5.5 * np. arange( num_samples) )
sinusoid_windowed = sinusoid * np. hanning( len ( sinusoid) )
fig, axes = plt. subplots( 1 , 2 )
for idx, ( win, wav) in enumerate ( [ ( "rectangular" , sinusoid) , ( "hann" , sinusoid_windowed) ] ) :
color = [ "k" , "g" ] [ idx]
X = DFT( wav)
axes[ 0 ] . stem( np. arange( num_samples) , wav, color, markerfmt = color+ 'o' , basefmt = " " , use_line_collection = True , label = win)
axes[ 0 ] . set ( title= "Discrete signal" , xlabel= "Discrete index" , ylabel= "Amplitude" )
plot_magnitude( X, axes[ 1 ] , wav. shape[ - 1 ] , c= color, label = win)
fig. set_size_inches( 10 , 4 )
fig. tight_layout( )
plt. legend( )
plt. show( )
hann window를 취하면 진폭의 크기도 작아지지만 side lobe(주변 진폭)의 크기가 굉장히 작아짐
spectral leakage 완화
원래 신호에는 포함되어 있지 않은 주파수 성분이 관측되는 현상
신호에 window를 취해 FFT 수행
rectangular window(no window)를 취했을 때 spectral leakage가 나오는 이유
단일 sinusoid signal에 FFT를 수행해도 frequency(주파수)가 1개로 나오지 않음
윈도우를 아무것도 적용하지 않은 경우 time domain에서 signal과 rectangular window의 element-wise multiplication이다.
이를 주파수 도메인에서 보면 convolution 연산으로 바뀜
즉, 원래 신호의 스펙트럼에 rectangular window의 스펙트럼이 convolution되기 때문에 spectral leakage 발생
hann window가 rectangular window보다 leakage가 적게 발생하는 이유
Ceptrum Analysis
Speech Model
발음을 결정하는 소리의 최소 단위인 음소는 크게 2가지로 구분
유성음(voiced): 발성할 때 성대의 진동을 동반
무성음(unvoiced): 진동 없이 성대를 통과
여기 신호(excitation signal): 성대를 막 통과한 소리
유성음 여기 신호의 파형은 준주기성을 띔
성대의 진동 속도에 따라 고유의 기본 주파수 와 기본 주파수의 배수에 해당하는 여러 배음 들로 전체 스펙트럼이 구성
무성음 여기 신호의 파형은 성대가 진동하지 않아 다양한 주파수 성분 이 고르게 포함된 백색소음 과 같은 스펙트럼
처음 폐에서 만드는 압축된 공기는 백색소음에 가까운 비주기성 신호
정규분포와 같이 쉽게 사용할 수 있는 확률분포로 모델링 가능
여기 신호는 유성음/무성음 여부에 따라 구분
성도(vocal tract - 목, 코, 입, 혀 등)을 통과하며 발음 결정
발음마다 성도의 구조가 달라져 증폭되는 주파수 대역과 감쇠되는 대역 역시 달라짐
이를 스펙트럼 포락선(spectral envelope)이라고 함
Ceptrum Analysis
발음 혹은 음색 등을 결정하는 주요한 특징으로는 spectral envelope이 있다.
이를 얻기 위해서는 DFT → \rarr → log → \rarr → IDFT → \rarr → liftering → \rarr → DFT 형태로 연산
S ( f ) = X ( f ) H ( f ) log ∣ S ( f ) ∣ = log ∣ X ( f ) H ( f ) ∣ = log ∣ X ( f ) ∣ + log ∣ H ( f ) ∣ S(f) = X(f)H(f)\\ \log{|S(f)|} = \log{|X(f)H(f)|} = \log{|X(f)|} + \log{|H(f)|} S ( f ) = X ( f ) H ( f ) log ∣ S ( f ) ∣ = log ∣ X ( f ) H ( f ) ∣ = log ∣ X ( f ) ∣ + log ∣ H ( f ) ∣
S ( f ) S(f) S ( f ) : 음성 신호의 스펙트럼, X ( f ) X(f) X ( f ) : 빠른 신호의 스펙트럼, H ( f ) H(f) H ( f ) : 느린 신호의 스펙트럼
로그를 취할 시, 빠른 신호와 느린 신호가 덧셈의 형태로 존재하게 됨
로그를 취한 신호를 IDF를 취하면 느린 신호는 time축의 낮은 영역, 빠른 신호는 time축의 높은 영역에 위치
시간축과 주파수축의 duality 성질 때문
그 결과는 마치 주파수축의 모양을 주파수 분해한 것과 같은 상태가 됨
그래서 spec을 거꾸로해 cepstrum이라고 이름이 붙여짐
여기서 envelope을 얻기 위해 time축의 낮은 영역을 가져옴
주파수 축에서 lowband를 가져오는 것과 비슷한 형태이기 때문에 filtering의 이름을 바꿔 liftering이라고 부름
해당 신호를 다시 DFT하면 spectral envelope을 얻을 수 있음
MFCC
Mel Scale
인간은 인접한 주파수를 크게 구별하지 못함
인지기관이 categorical한 구분을 하기 때문
주파수들의 bin 그룹을 만들고 이를 합하는 방식 이용
인간은 일반적으로 낮은 주파수에 더 풍부한 정보 사용
주파수가 올라갈수록 필터의 폭이 높아지며 고주파는 거의 고려를 하지 않음
고주파에서 저주파로 내려갈수록 담당하는 주파수 대역이 점점 조밀해짐
멜 스펙트럼은 주파수 단위를 아래 공식에 따라 멜 단위로 바꾼 것
일반적으로는 mel-scaled bin을 FFT size보다 조금 더 작게 만듦
Mel Filter Bank를 사용해 구현
m = 2595 log 10 ( 1 + f 700 ) m = 2595 \log_{10} (1+\cfrac{f}{700}) m = 2 5 9 5 log 1 0 ( 1 + 7 0 0 f )
filter_banks = np. dot( pow_frames, fbank. T)
filter_banks = np. where( filter_banks== 0 , np. finfo( float ) , eps, filter_banks)
MFCC(Mel-frequency Cepstral Coefficients)
멜 스펙트럼 혹은 로그 멜 스펙트럼은 태생적으로 feature 내 변수 간 상관관계 존재
몇 개의 헤르츠 기준 주파수 영역대 에너지를 한데 모아보기 때문
변수 간의 종속성 해결을 위해 로그 멜 스펙트럼에 역푸리에 변환을 수행한 feature가 MFCC
waveform → \rarr → Magnitude Spectrogram (by STFT) → \rarr → Mel Spectrogram (by Mel Filterbank) → \rarr → Log-Mel Spectrogram → \rarr → MFCC (by Discrete Cosine Transform)
DCT(Discrete Cosine Transform): n n n 개의 데이터를 n n n 개의 코사인 함수의 합으로 표현해 데이터 양을 줄이는 방식
상관관계가 높았던 주파수 도메인 정보가 새로운 도메인으로 바뀌어 상관관계 완화
MFCC는 멜 스펙트럼보다 버려지는 정보가 많다.
그렇기 때문에 주로 멜 스펙트럼 혹은 로그 멜 스펙트럼 사용
import librosa. display
fig, axes = plt. subplots( 2 )
wav, sr = librosa. load( librosa. ex( "choice" ) , duration = 5 )
S = librosa. feature. melspectrogram( wav, sr= sr, n_mels = 256 )
log_S = librosa. power_to_db( S, ref= np. max )
mel_img = librosa. display. specshow( log_S, sr= sr, x_axis= 'time' , y_axis= 'mel' , ax = axes[ 0 ] )
axes[ 0 ] . set_title( 'Mel power sepctrogram' )
fig. colorbar( mel_img, format = '%+02.0f dB' , ax = axes[ 0 ] )
mfcc = librosa. feature. mfcc( S= librosa. power_to_db( mel_S) , n_mfcc= 13 )
mfcc = mfcc. astype( np. float32)
plt. figure( figsize= ( 12 , 4 ) )
mfcc_img = librosa. display. specshow( mfcc, ax = axes[ 1 ] , x_axis= 'time' )
fig. colorbar( mfcc_img, ax= axes[ 1 ] )
axes[ 1 ] . set_title( 'MFCC' )
fig. set_size_inches( 10 , 8 )
fig. tight_layout( )
plt. show( )
Data Preprocessing
Data Augmentation
def change_pitch ( data, sr) :
y_pitch = data. copy( )
bins_per_octave = 12
pitch_pm = 2
pitch_change = pitch_pm * 2 * ( np. random. uniform( ) )
y_pitch = librosa. effects. pitch_shift( y_pitch. astype( 'float64' ) , sr, n_steps= pitch_change,
bins_per_octave= bins_per_octave)
return y_pitch
def value_aug ( data) :
y_aug = data. copy( )
dyn_change = np. random. uniform( low= 1.5 , high= 3 )
y_aug = y_aug * dyn_change
return y_aug
def add_noise ( data) :
noise = np. random. randn( len ( data) )
data_noise = data + 0.005 * noise
return data_noise
Decompose harmonic and percussive components
def hpss ( data) :
y_harmonic, y_percussive = librosa. effects. hpss( data. astype( 'float64' ) )
return y_harmonic, y_percussive
def shift ( data) :
return np. roll( data, 1600 )
def change_pitch_and_speed ( data) :
y_pitch_speed = data. copy( )
length_change = np. random. uniform( low= 0.8 , high= 1 )
speed_fac = 1.0 / length_change
tmp = np. interp( np. arange( 0 , len ( y_pitch_speed) , speed_fac) , np. arange( 0 , len ( y_pitch_speed) ) , y_pitch_speed)
minlen = min ( y_pitch_speed. shape[ 0 ] , tmp. shape[ 0 ] )
y_pitch_speed *= 0
y_pitch_speed[ 0 : minlen] = tmp[ 0 : minlen]
return y_pitch_speed
Tip! use torch.nn.Sequential
train_audio_transforms = torch. nn. Sequential(
torchaudio. transforms. MelSpectrogram( sample_rate= 16000 , n_mels= 128 ) ,
torchaudio. transforms. FrequencyMasking( freq_mask_param= 15 , iid_masks= False ) ,
torchaudio. transforms. TimeMasking( time_mask_param= 35 , iid_masks= False )
)
Filter
def butter_pass ( cutoff, fs, btype, order= 5 ) :
nyq = 0.5 * fs
normal_cutoff = cutoff / nyq
b, a = butter( order, normal_cutoff, btype= btype, analog= False )
return b, a
def butter_filter ( data, cutoff, fs, btype, order= 5 ) :
b, a = butter_pass( cutoff, fs, btype, order= order)
y = lfilter( b, a, data)
return y
참고
https://velog.io/@tobigsvoice1516/wk1DSP
https://www.youtube.com/watch?v=RxbkEjV7c0o&list=PL9mhQYIlKEhem5_wrQqDtNqNcaDyFrYGN