Audio Task

  1. 화자 인식
  2. voice agent wake
  3. 음성을 input으로 받아 디바이스 제어
  4. 명령을 실행한 결과 음성 합성을 통해 출력
  • 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
              • A: 진폭
          • 주파수: 주기적인 현상이 단위 시간 동안 몇 번 일어났는 지를 의미
            • Hz(헤르츠), rpm, BPM 등 단위 사용
            • 파장과 역수 관계
          • 위상: 반복되는 파형의 한 주기에서 첫 시작점의 각도 혹은 어느 한 순간의 위치
          • 파장(Wave Length): 파동의 한 번의 주기가 갖는 길이
  • 심리 음향
    • 소리의 물리적 특성이 동일하더라도 종종 청자에 따라 다르게 인식
      • 소리라는 물리적 현상을 인간은 주관적이고 감각적으로 인식하는 것
  • 복잡파(Complex Wave)
    • 우리가 사용하는 대부분의 소리는 복합파
    • 복수의 서로 다른 정현파들의 합으로 이루어진 파형
      • 정현파: 일종의 복소 주기 함수(복소수가 있는 주기 함수)
  • Sinusoid
    • 정현파(Sinusoid)는 주기 신호를 총칭
    • 오일러 공식: eiθ=cosθ+isinθe^{i \theta} = \cos{\theta} + i \sin{\theta}
      • 세계에서 가장 아름다운 공식
      • 복소수 지수를 정의하는 데에 출발점이 되며, 삼각 함수와 지수 함수에 대한 관계를 나타냄
      • θ\theta: 실수
      • θ=π\theta = \pi를 대입해 오일러 등식 구함
        • 오일러 등식: eiπ+1=0e^{i \pi} + 1 = 0
    • 삼각 함수에 의한 표현: x(t)=Acos(w0t+Φ)x(t) = A \cos{(w_0 t + \Phi)}
    • 복소지수(복소수 지수)에 의한 표현: x(t)=Re{Aei(w0t+Φ)}=Aei(w0t+Φ)+Aei(w0t+Φ)2x(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}
    • AA: 진폭, w0w_0: 주파(=2ϕf= 2 \phi f), Φ\Phi: 위상
import librosa.display

def sinusoid(t, f, phi = 0):
    # cosine으로 표현한 sinusoid
    return np.cos(2*np.pi*f*t + phi) 

def complex_sinusoid(t, f, phi = 0):
    # 복소수로 표현한 sinusoid
    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):
    # dur을 균일하게 sr개로 나눔: (간격=1/sr)
    return np.arange(sr*dur) / sr

def sinusoid_samples(sr, f = 8000, dur = 1, isComplex = False):
    # sampling할 위치 지정
    ts = uniform_samples(sr, dur)
    # 해당 위치의 sinusoid값 계산
    if isComplex:
        sin = complex_sinusoid(ts, f)
    else:
        sin = sinusoid(ts, f)
    return ts, sin

sr = 48000

fig, ax = plt.subplots(2, sharex = 'all')

# 실수 sinusoid
# sinusoid sampling: sampling rate = 48,000
ts, sin = sinusoid_samples(sr, isComplex = False)

# sinusoid 전방 100개 시각화
ax[0].plot(ts[:100], sin[:100])
ax[0].set_title("Real Sinusoids")

# 복소수 sinusoid
# sinusoid sampling: sampling rate = 48,000
cts, csin = sinusoid_samples(sr, isComplex = True)
# sinusoid 전방 100개 시각화
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 fsf_s !!
    • sampling rate이 클수록 원본 데이터와 비슷
      • but, 저장량이 커짐
    • 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)

# sampling rate에 따른 소리 비교
# sampling_rate이 높을 수록 소리가 고급지다.
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: 2B1-2^{B-1} ~ $ 2^{B-1}-1$
    • Audio CD의 Quantization(16 bits): 215-2^{15}~21512^{15}-1
      • 해당 값들은 보통 1.0-1.0 ~ 1.01.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))

Fourier Transforms(푸리에 변환)

  • 임의의 입력 신호를 다양한 주파수를 갖는 주기 함수들의 합으로 분해해 표현
    • 각 주기 함수들의 진폭을 구하는 과정
    • 푸리에는 임의의 함수를 삼각 함수의 무한 선형 조합으로 표현할 수 있다고 주장하였다.
  • 힐베르트 공간 F=L2([0,2π])F = L^2([0, 2\pi]) 상에서 지수함수계 E=12πejnt:n=0,1,±1,±2,E = {\cfrac{1}{\sqrt{2\pi}}e^{jnt}: n=0, 1, \pm1, \pm2, \dots}는 완비정규직교 기저 집합이므로 [0,2π][0,2\pi]에서 정의되는 주기함수들은 다음과 같이 푸리에 급수로 표현
    • 힐베르트 공간: 모든 코시 열(점 사이의 거리가 점점 가까워지는 수열)의 극한이 존재하는 내적 공간
      • 유클리드 공간을 일반화한 개념
    • 완비정규직교 기저 집합
      • 기저: 벡터 공간을 만들어 낼 수 있는 가장 작은 것
x(t)=Σn=an12πejntan=<f,pn>=02πx(t)12πejntdtx(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
  • 첫 번째 식에서 nn-\infin ~ \infin 의 범위를 갖고 움직임
    • 어떠한 신호가 서로 다른 주기 함수들의 합으로 표현되는데, 그 주기 함수는 무한개가 있어야 함
    • 지수함수계 함수들의 모두 직교
      • x(t)x(t)는 기저를 EE로 두면, (a,,a)(a_{-\infin}, \dots, a_{\infin})라는 좌표로 표현 가능

DFT (Discrete Fourier Transform)

Fourier Transform

  • 주기가 무한대인 일반적인 함수로 일반화하면, Fourier Transform 식을 얻을 수 있다.
Inverse_Fourier_Transform:x(t)=12πX(jw)ejwtdwFourier_Transform:X(jw)=x(t)ejwtdtInverse\_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
  • time domain에 연속적이고 무한한 길이의 신호를 frequency domain에서 연속적이고 무한한 주파수로 나타낼 수 있다.

Discrete Fourier Transform

  • 우리가 sampling한 신호는 시간의 간격과 소리의 진폭이 모두 discrete한 데이터
    • 푸리에 변환 식을 Discrete하게 바꿔야 함
Inverse_Discrete_Fourier_Transform:x[t]=1NΣk=0N1X[k]ejwknDiscrete_Fourier_Transform:X[k]=Σn=0N1x[n]ejwknInverse\_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}
  • 우리가 가진 신호 x[t]x[t]에서 이산 시계열 데이터가 주기 N으로 반복된다고 할 때, DFT는 주파수와 진폭이 서로 다른 N개의 사인 함수의 합으로 표현
  • 스펙트럼 X[k]X[k]를 원래 시계열 데이터에 대한 퓨리에 변환값이라고 함

Zero Padding

  • 신호의 길이가 잛으면 DFT된 신호가 정확히 표현되지 않는다.
    • zero-padding 사용
  • x(n)x(n) 뒤에 0을 여러개 덧붙여서 강제로 길이 늘림
    • 더욱 촘촘하게 표현
  • 신호의 뒤에 0을 붙여 DFT를 수행하면, 샘플 수가 늘어난 X(m)X(m)을 얻음
    • 고밀도의 스펙트럼 얻을 수 있음
    • 신호의 샘플 수가 늘어나게 되어 X(m)X(m)이 더욱 촘촘한 간격의 한 주기 신호로 생성
    • 단순히 0을 덧붙여 특성이나 분해능에는 영향 없음
      • 분해능: 서로 떨어져 있는 두 물체를 서로 구별할 수 있는 능력
  • 즉, 고밀도의 스펙트럼을 얻을 수는 있지만 고분해능의 스펙트럼을 얻을 수 있는 것은 아님
# Discrete 푸리에 변환
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)) # sinusoid 정의
X = DFT(sinusoid) # sinusoid 푸리에 변환

fig, axes = plt.subplots(ncols=3)
# sinusoid 시각화
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")
# magnitude 시각화
plot_magnitude(X, axes[1], sinusoid.shape[-1])
# phase 시각화
plot_phase(X, axes[2], sinusoid.shape[-1])
fig.set_size_inches(18, 6)
fig.tight_layout()
plt.show()

STFT(Short Time Fourier Transform)

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는 어느 시간대에 주파수가 변하는지 알 수 없음
      • Time domain에 대한 정보 사라짐
    • STFT는 시간을 프레임 별로 나눠 FFT 수행

STFT

X(l,k)=Σn=0N1w(n)x(n+lH)e2πknNX(l,k) = \Sigma^{N-1}_{n=0} w(n)x(n+lH)e^{\cfrac{-2\pi kn}{N}}
  • 주파수의 특성이 시간에 따라 달라지는 사운드를 분석하는 방법
  • 시계열 데이터를 일정한 시간 구간(window-size)로 나누고, 각 구간에 대해 스펙트럼을 구함
    • (time, frequency, magnitude) 꼴로 spectrogram이 나옴
    • 시간을 x축, 주파수를 y축, 크기를 색으로 표시
  • 파라미터
    • NN: FFT size
      • window를 얼마나 많은 주파수 밴드로 나누는가
    • w(n)w(n): windown function
      • 일반적으로 Hann window가 쓰임
    • nn: window size
      • window함수에 들어가는 sample의 양
      • 작을수록 low-frequency resolution을 갖고, high-time resolution을 가짐
      • 길수록 high-frequency resolution, low-time resolution을 가짐
    • HH: hop size
      • 윈도우가 겹치는 사이즈
      • 일반적으로 1/2, 1/4 정도 겹치게 함

Convolution Theorem

  • Convolution
(xy)[n]:=Σm=xm]y[nm](xy)(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
  • Circular Convolution
(xy)[n]:=Σm=0N1x[m]y[(nm)N](xy)(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
  • 한 도메인에서의 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)logS(f)=logX(f)H(f)=logX(f)+logH(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): 음성 신호의 스펙트럼, X(f)X(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=2595log10(1+f700)m = 2595 \log_{10} (1+\cfrac{f}{700})
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): nn개의 데이터를 nn개의 코사인 함수의 합으로 표현해 데이터 양을 줄이는 방식
    • 상관관계가 높았던 주파수 도메인 정보가 새로운 도메인으로 바뀌어 상관관계 완화
    • 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

  • Change Pitch
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
  • Change Amplitude
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
  • Add Noise
def add_noise(data):
    noise = np.random.randn(len(data))
    data_noise = data + 0.005 * noise
    return data_noise
  • Decompose harmonic and percussive components
# 보통 harmonic part만 사용한다.
def hpss(data):
    y_harmonic, y_percussive = librosa.effects.hpss(data.astype('float64'))
    return y_harmonic, y_percussive
  • Shift
def shift(data):
    return np.roll(data, 1600)
  • Change Pitch & Speed
def change_pitch_and_speed(data):
    y_pitch_speed = data.copy()
    # you can change low and high here
    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),  #freq_mask_param: masking의 크기    iid_masks: 배치마다 같은 마스킹을 줄것인가?
      torchaudio.transforms.TimeMasking(time_mask_param=35, iid_masks=False)
)

Filter

# btype: {‘lowpass’, ‘highpass’, ‘bandpass’, ‘bandstop’}
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

profile
데이터사이언스를 공부하는 권유진입니다.

0개의 댓글