您當前的位置:首頁 > 曲藝

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

作者:由 mashuangwe 發表于 曲藝時間:2020-04-15

語音識別中常用的音訊特徵包括fbank與mfcc。

獲得語音訊號的fbank特徵的一般步驟是:預加重、分幀、加窗、短時傅立葉變換(STFT)、mel濾波、去均值等。對fbank做離散餘弦變換(DCT)即可獲得mfcc特徵。

下面透過程式碼進行分析說明。

1、導包

# 導包

import numpy as np

from scipy。io import wavfile

from scipy。fftpack import dct

import matplotlib。pyplot as plt

2、繪圖函式

繪製時域圖

def plot_time(sig, fs):

time = np。arange(0, len(sig)) * (1。0 / fs)

plt。figure(figsize=(20, 5))

plt。plot(time, sig)

plt。xlabel(‘Time(s)’)

plt。ylabel(‘Amplitude’)

plt。grid()

繪製頻域圖

def plot_freq(sig, sample_rate, nfft=512):

xf = np。fft。rfft(sig, nfft) / nfft

freqs = np。linspace(0, sample_rate/2, nfft/2 + 1)

xfp = 20 * np。log10(np。clip(np。abs(xf), 1e-20, 1e100))

plt。figure(figsize=(20, 5))

plt。plot(freqs, xfp)

plt。xlabel(‘Freq(hz)’)

plt。ylabel(‘dB’)

plt。grid()

繪製二維陣列

def plot_spectrogram(spec, notylabele):

fig = plt。figure(figsize=(20, 5))

heatmap = plt。pcolor(spec)

fig。colorbar(mappable=heatmap)

plt。xlabel(‘Time(s)’)

plt。ylabel(ylabel)

plt。tight_layout()

plt。show()

3、資料讀取

音訊訊號連結:OSR_us_000_0010_8k。wav

wav_file = ‘OSR_us_000_0010_8k。wav’

fs, sig = wavfile。read(wav_file)

# 保留前3。5s資料

sig = sig[0: int(3。5 * fs)]

plot_time(sig, fs)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

plot_freq(sig, fs)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

4、預加重

pre_emphasis = 0。97

sig = np。append(sig[0], sig[1:] - pre_emphasis * sig[:-1])

plot_time(sig, fs)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

plot_freq(sig, fs)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

從上面這個圖來看,確實起到了平衡頻譜的作用。

5、分幀

def framing(frame_len_s, frame_shift_s, fs, sig):

“”“

分幀,主要是計算對應下標

:param frame_len_s: 幀長,s

:param frame_shift_s: 幀移,s

:param fs: 取樣率,hz

:param sig: 訊號

:return: 二維list,一個元素為一幀訊號

”“”

sig_n = len(sig)

frame_len_n, frame_shift_n = int(round(fs * frame_len_s)), int(round(fs * frame_shift_s))

num_frame = int(np。ceil(float(sig_n - frame_len_n) / frame_shift_n) + 1)

pad_num = frame_shift_n * (num_frame - 1) + frame_len_n - sig_n # 待補0的個數

pad_zero = np。zeros(int(pad_num)) # 補0

pad_sig = np。append(sig, pad_zero)

# 計算下標

# 每個幀的內部下標

frame_inner_index = np。arange(0, frame_len_n)

# 分幀後的訊號每個幀的起始下標

frame_index = np。arange(0, num_frame) * frame_shift_n

# 複製每個幀的內部下標,訊號有多少幀,就複製多少個,在行方向上進行復制

frame_inner_index_extend = np。tile(frame_inner_index, (num_frame, 1))

# 各幀起始下標擴充套件維度,便於後續相加

frame_index_extend = np。expand_dims(frame_index, 1)

# 分幀後各幀的下標,二維陣列,一個元素為一幀的下標

each_frame_index = frame_inner_index_extend + frame_index_extend

each_frame_index = each_frame_index。astype(np。int, copy=False)

frame_sig = pad_sig[each_frame_index]

return frame_sig

frame_len_s = 0。025

frame_shift_s = 0。01

frame_sig = framing(frame_len_s, frame_shift_s, fs, sig)

6、加窗

在分幀之後,通常需要對每幀的訊號進行加窗處理。目的是讓幀兩端平滑地衰減,這樣可以降低後續傅立葉變換後旁瓣的強度,取得更高質量的頻譜。常用的窗有:矩形窗、漢明(Hamming)窗、漢寧窗(Hanning),以漢明窗為例,其窗函式為:

w(n) = 0.54-0.46*cos(\frac{2\pi n}{N - 1})

這裡

0\leq n \leq N-1

N

是窗的寬度。

# 加窗

window = np。hamming(int(round(frame_len_s * fs)))

plt。figure(figsize=(20, 5))

plt。plot(window)

plt。grid()

plt。xlim(0, 200)

plt。ylim(0, 1)

plt。xlabel(‘Samples’)

plt。ylabel(‘Amplitude’)

frame_sig *= window

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

plot_time(frame_sig[1], fs)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

plot_freq(frame_sig[1], fs)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

7、短時傅立葉變換(STFT)

對於每一幀的加窗訊號,進行N點FFT變換,也稱短時傅立葉變換(STFT),N通常取256或512。

def stft(frame_sig, nfft=512):

“”“

:param frame_sig: 分幀後的訊號

:param nfft: fft點數

:return: 返回分幀訊號的功率譜

np。fft。fft vs np。fft。rfft

fft 返回 nfft

rfft 返回 nfft // 2 + 1,即rfft僅返回有效部分

”“”

frame_spec = np。fft。rfft(frame_sig, nfft)

# 幅度譜

frame_mag = np。abs(frame_spec)

# 功率譜

frame_pow = (frame_mag ** 2) * 1。0 / nfft

return frame_pow

nfft = 512

frame_pow = stft(frame_sig, nfft)

plt。figure(figsize=(20, 5))

plt。plot(frame_pow[1])

plt。grid()

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

8、mel濾波

經過上面的步驟之後,在能量譜上應用Mel濾波器組,就能提取到FBank特徵。

在介紹Mel濾波器組之前,先介紹一下Mel刻度,這是一個能模擬人耳接收聲音規律的刻度,人耳在接收聲音時呈現非線性狀態,對高頻的更不敏感,因此Mel刻度在低頻區分辨度較高,在高頻區分辨度較低,與頻率之間的換算關係為:

m = 2595 * log10(1 + f/700)

f=700*(10^{m/2595}-1)

Mel濾波器組就是一系列的三角形濾波器,通常有40個或80個,在中心頻率點響應值為1,在兩邊的濾波器中心點衰減到0,如下圖:

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

具體公式可以寫為:

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

def mel_filter(frame_pow, fs, n_filter, nfft):

“”“

mel 濾波器係數計算

:param frame_pow: 分幀訊號功率譜

:param fs: 取樣率 hz

:param n_filter: 濾波器個數

:param nfft: fft點數

:return: 分幀訊號功率譜mel濾波後的值的對數值

mel = 2595 * log10(1 + f/700) # 頻率到mel值對映

f = 700 * (10^(m/2595) - 1 # mel值到頻率對映

上述過程本質上是對頻率f對數化

”“”

mel_min = 0 # 最低mel值

mel_max = 2595 * np。log10(1 + fs / 2。0 / 700) # 最高mel值,最大訊號頻率為 fs/2

mel_points = np。linspace(mel_min, mel_max, n_filter + 2) # n_filter個mel值均勻分佈與最低與最高mel值之間

hz_points = 700 * (10 ** (mel_points / 2595。0) - 1) # mel值對應回頻率點,頻率間隔指數化

filter_edge = np。floor(hz_points * (nfft + 1) / fs) # 對應到fft的點數比例上

# 求mel濾波器係數

fbank = np。zeros((n_filter, int(nfft / 2 + 1)))

for m in range(1, 1 + n_filter):

f_left = int(filter_edge[m - 1]) # 左邊界點

f_center = int(filter_edge[m]) # 中心點

f_right = int(filter_edge[m + 1]) # 右邊界點

for k in range(f_left, f_center):

fbank[m - 1, k] = (k - f_left) / (f_center - f_left)

for k in range(f_center, f_right):

fbank[m - 1, k] = (f_right - k) / (f_right - f_center)

# mel 濾波

# [num_frame, nfft/2 + 1] * [nfft/2 + 1, n_filter] = [num_frame, n_filter]

filter_banks = np。dot(frame_pow, fbank。T)

filter_banks = np。where(filter_banks == 0, np。finfo(float)。eps, filter_banks)

# 取對數

filter_banks = 20 * np。log10(filter_banks) # dB

return filter_banks

# mel 濾波

n_filter = 40 # mel濾波器個數

filter_banks = mel_filter(frame_pow, fs, n_filter, nfft)

plot_spectrogram(filter_banks。T, ylabel=‘Filter Banks’)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

9、去均值

去均值的目的是希望減少訓練集與測試集之間的不匹配,均衡頻譜,提升信噪比。

# 去均值

filter_banks -= (np。mean(filter_banks, axis=0) + 1e-8)

plot_spectrogram(filter_banks。T, ylabel=‘Filter Banks’)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

去均值之後的fbank

10、離散餘弦變換(DCT)

前面提取到的FBank特徵,往往是高度相關的。因此可以繼續用DCT變換,將這些相關的濾波器組係數進行壓縮。對於ASR來說,通常取2~13維,扔掉的資訊裡面包含濾波器組係數快速變化部分。

num_ceps = 12

mfcc = dct(filter_banks, type=2, axis=1, norm=‘ortho’)[:, 1:(num_ceps+1)]

plot_spectrogram(mfcc。T, ‘MFCC Coefficients’)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

從圖中可以看出,DCT使得fbank的很多高頻部分被濾除掉了。

同樣可以對mfcc進行去均值操作。

mfcc -= (np。mean(mfcc, axis=0) + 1e-8)

plot_spectrogram(mfcc。T, ‘MFCC Coefficients’)

語音識別之——音訊特徵fbank與mfcc,程式碼實現與分析

去均值之後的mfcc

11、fbank與mfcc的比較

fbank特徵更多是希望符合聲音訊號的本質,擬合人耳的接收特性。

DCT是線性變換,會丟失語音訊號中原本的一些高度非線性成分。在深度學習之前,受限於演算法,mfcc配GMMs-HMMs是ASR的主流做法。當深度學習方法出來之後,由於神經網路對高度相關的資訊不敏感,mfcc不是最優選擇,經過實際驗證,其在神經網路中的表現也明顯不如fbank。

有人質疑是否需要傅立葉變換,其根據是傅立葉變換也是一種線性變換,也會使得語音訊號中的非線性成分丟失,因此是不是有可能對時域訊號進行直接處理的效果會更好。經實驗證明,訊號經過傅立葉變換後比直接處理時域訊號的效果更好,這是因為傅立葉變換本身不容易被擬合,不可避免地會增加模型的複雜度。此外,傅立葉變換是在短時上應用的,可以假設訊號在短時內是線性的,因此,傅立葉變換的線性不會造成很嚴重的問題。

總結

本文分析了音訊訊號特徵fbank與mfcc,進行了程式碼實現與視覺化。

DCT造成音訊訊號的高度非線性成分丟失嚴重,實驗已經證明,mfcc在對高度相關資訊不敏感的神經網路中的效果不如fbank。

傅立葉變換應用在短時上,其本身的線性不會造成嚴重問題,實驗證明,傅立葉變換的使用有利於模型效果的提升。

參考

Speech Processing for Machine Learning: Filter banks, Mel-Frequency Cepstral Coefficients (MFCCs) and What‘s In-Between

ASR中常用的語音特徵之FBank和MFCC(原理 + Python實現)

英國愛丁堡大學ASR課程講義

一個成熟的python提取這些特徵的包

標簽: Frame  SIG  np  FILTER  plt