Skip to content

Commit 8f0971e

Browse files
author
Ryoichi Takashima
committed
commit all files
1 parent 2918d93 commit 8f0971e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+14999
-0
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
data
2+
*/exp*
3+
*/backup*
4+
*/fbank
5+
*/mfcc
6+
*/__pycache__
7+
*/*.png

01compute_features/01_compute_fbank.py

+382
Large diffs are not rendered by default.

01compute_features/01_compute_mfcc.py

+384
Large diffs are not rendered by default.
+336
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# -*- coding: utf-8 -*-
2+
3+
#
4+
# MFCC特徴の計算を行います.
5+
#
6+
7+
# wavデータを読み込むためのモジュール(wave)をインポート
8+
import wave
9+
10+
# 数値演算用モジュール(numpy)をインポート
11+
import numpy as np
12+
13+
# os, sysモジュールをインポート
14+
import os
15+
import sys
16+
17+
#
18+
# 特徴量(FBANK, MFCC)を抽出するクラス
19+
#
20+
# sample_frequency: 入力波形のサンプリング周波数 [Hz]
21+
# frame_length: 分析窓幅 [ミリ秒]
22+
# frame_shift: 分析間隔(フレームシフト) [ミリ秒]
23+
# num_mel_bins: メルフィルタバンクの数(=FBANK特徴の次元数)
24+
# num_ceps: MFCC特徴の次元数(0次元目を含む)
25+
# lifter_coef: リフタリング処理のパラメータ
26+
# low_frequency: 低周波数帯域除去のカットオフ周波数 [Hz]
27+
# dither: ディザリング処理のパラメータ(雑音の強さ)
28+
#
29+
class FeatureExtractor():
30+
# クラスを呼び出した時点で最初に1回実行される関数
31+
def __init__(self, sample_frequency=16000, frame_length=25, frame_shift=10, num_mel_bins=23, num_ceps=13, lifter_coef=22, low_frequency=20, dither=1.0):
32+
# サンプリング周波数[Hz]
33+
self.sample_freq = sample_frequency
34+
# 窓幅をミリ秒からサンプル数へ変換
35+
self.frame_size = int(sample_frequency * frame_length * 0.001)
36+
# フレームシフトをミリ秒からサンプル数へ変換
37+
self.frame_shift = int(sample_frequency * frame_shift * 0.001)
38+
# メルフィルタバンクの数
39+
self.num_mel_bins = num_mel_bins
40+
# MFCCの次元数(0次含む)
41+
self.num_ceps = num_ceps
42+
# リフタリングのパラメータ
43+
self.lifter_coef = lifter_coef
44+
# 低周波数帯域除去のカットオフ周波数[Hz]
45+
self.low_frequency = low_frequency
46+
# ディザリング係数
47+
self.dither_coef = dither
48+
49+
# FFTのポイント数 = 窓幅以上の2のべき乗
50+
self.fft_size = 1
51+
while self.fft_size < self.frame_size:
52+
self.fft_size *= 2
53+
54+
# メルフィルタバンクを作成する
55+
self.mel_filter_bank = self.MakeMelFilterBank()
56+
57+
# 離散コサイン変換(DCT)の基底行列を作成する
58+
self.dct_matrix = self.MakeDCTMatrix()
59+
60+
# リフタ(lifter)を作成する
61+
self.lifter = self.MakeLifter()
62+
63+
#
64+
# 周波数をヘルツからメルに変換する
65+
#
66+
def Herz2Mel(self, herz):
67+
return (1127.0 * np.log(1.0 + herz / 700))
68+
69+
#
70+
# メルフィルタバンクを作成する
71+
#
72+
def MakeMelFilterBank(self):
73+
# メル軸での最大周波数
74+
mel_high_freq = self.Herz2Mel(self.sample_freq/2)
75+
# メル軸での最小周波数
76+
mel_low_freq = self.Herz2Mel(self.low_frequency)
77+
# 最小から最大周波数まで,メル軸上での等間隔な周波数を得る
78+
mel_points = np.linspace(mel_low_freq, mel_high_freq, self.num_mel_bins+2)
79+
80+
# パワースペクトルの次元数 = FFTサイズ/2+1
81+
# ※Kaldiの実装ではナイキスト周波数成分(最後の+1)は捨てているが,本実装では捨てずに用いている
82+
dim_spectrum = int(self.fft_size / 2) + 1
83+
84+
# メルフィルタバンク(フィルタの数 x スペクトルの次元数)
85+
mel_filter_bank = np.zeros((self.num_mel_bins, dim_spectrum))
86+
for m in range(self.num_mel_bins):
87+
# 三角フィルタの左端,中央,右端のメル周波数
88+
left_mel = mel_points[m]
89+
center_mel = mel_points[m+1]
90+
right_mel = mel_points[m+2]
91+
# パワースペクトルの各ビンに対応する重みを計算する
92+
for n in range(dim_spectrum):
93+
# 各ビンに対応するヘルツ軸周波数を計算
94+
freq = 1.0 * n * self.sample_freq/2 / dim_spectrum
95+
# メル周波数に変換
96+
mel = self.Herz2Mel(freq)
97+
# そのビンが三角フィルタの範囲に入っていれば,重みを計算
98+
if mel > left_mel and mel < right_mel:
99+
if mel <= center_mel:
100+
weight = (mel - left_mel) / (center_mel - left_mel)
101+
else:
102+
weight = (right_mel-mel) / (right_mel-center_mel)
103+
mel_filter_bank[m][n] = weight
104+
105+
return mel_filter_bank
106+
107+
#
108+
# 1フレーム分の波形データを抽出し,前処理を実施する.また,対数パワーの値も計算する
109+
#
110+
def ExtractWindow(self, waveform, start_index, num_samples):
111+
112+
# waveformから,1フレーム分の波形を抽出する
113+
window = waveform[start_index:start_index + self.frame_size].copy()
114+
115+
# ディザリングを行う(-dither_coef~dither_coefの一様乱数を加える)
116+
if self.dither_coef > 0:
117+
window = window + np.random.rand(self.frame_size) * (2*self.dither_coef) - self.dither_coef
118+
119+
# 直流成分をカットする
120+
window = window - np.mean(window)
121+
122+
# 以降の処理を行う前に,パワーを求める
123+
power = np.sum(window ** 2)
124+
# 対数計算時に-infが出力されないよう,フロアリング処理を行う
125+
if power < 1E-10:
126+
power = 1E-10
127+
# 対数をとる
128+
log_power = np.log(power)
129+
130+
# プリエンファシス(高域強調) window[i] = 1.0 * window[i] - 0.97 * window[i-1]
131+
window = np.convolve(window,np.array([1.0, -0.97]), mode='same')
132+
# numpyの畳み込みでは0番目の要素が処理されない(window[i-1]が存在しないので)ため,
133+
# window[0-1]をwindow[0]で代用して処理する
134+
window[0] -= 0.97*window[0]
135+
136+
# hamming窓をかける hamming[i] = 0.54 - 0.46 * np.cos(2*np.pi*i / (self.frame_size - 1))
137+
window *= np.hamming(self.frame_size)
138+
139+
return window, log_power
140+
141+
#
142+
# メルフィルタバンク特徴(FBANK)を計算する
143+
# 出力1: fbank_features: メルフィルタバンク特徴
144+
# 出力2: log_power: 対数パワー値(MFCC抽出時に使用)
145+
#
146+
def ComputeFBANK(self, waveform):
147+
# 波形データの総サンプル数
148+
num_samples = np.size(waveform)
149+
# 特徴量の総フレーム数を計算する
150+
num_frames = (num_samples - self.frame_size) // self.frame_shift + 1
151+
# メルフィルタバンク特徴
152+
fbank_features = np.zeros((num_frames, self.num_mel_bins))
153+
# 対数パワー(MFCC特徴を求める際に使用する)
154+
log_power = np.zeros(num_frames)
155+
156+
# 1フレームずつ特徴量を計算する
157+
for frame in range(num_frames):
158+
# 分析の開始位置は,フレーム番号(0始まり)*フレームシフト
159+
start_index = frame * self.frame_shift
160+
# 1フレーム分の波形を抽出し,前処理を実施する.また対数パワーの値も得る
161+
window, log_pow = self.ExtractWindow(waveform, start_index, num_samples)
162+
163+
# 高速フーリエ変換(FFT)を実行
164+
spectrum = np.fft.fft(window, n=self.fft_size)
165+
# FFT結果の右半分(負の周波数成分)を取り除く
166+
# ※Kaldiの実装ではナイキスト周波数成分(最後の+1)は捨てているが,本実装では捨てずに用いている
167+
spectrum = spectrum[:int(self.fft_size/2) + 1]
168+
169+
# パワースペクトルを計算する
170+
spectrum = np.abs(spectrum) ** 2
171+
172+
# メルフィルタバンクを畳み込む
173+
fbank = np.dot(spectrum, self.mel_filter_bank.T)
174+
175+
# 対数計算時に-infが出力されないよう,フロアリング処理を行う
176+
fbank[fbank<0.1] = 0.1
177+
178+
# 対数をとってfbank_featuresに加える
179+
fbank_features[frame] = np.log(fbank)
180+
181+
# 対数パワーの値をlog_powerに加える
182+
log_power[frame] = log_pow
183+
184+
return fbank_features, log_power
185+
186+
#
187+
# 離散コサイン変換(DCT)の基底行列を作成する
188+
#
189+
def MakeDCTMatrix(self):
190+
N = self.num_mel_bins
191+
# DCT基底行列 (基底数(=MFCCの次元数) x FBANKの次元数)
192+
dct_matrix = np.zeros((self.num_ceps,self.num_mel_bins))
193+
for k in range(self.num_ceps):
194+
if k == 0:
195+
dct_matrix[k] = np.ones(self.num_mel_bins) * 1.0 / np.sqrt(N)
196+
else:
197+
dct_matrix[k] = np.sqrt(2/N) * np.cos(((2.0*np.arange(N)+1)*k*np.pi) / (2*N))
198+
199+
return dct_matrix
200+
201+
#
202+
# リフタを計算する
203+
#
204+
def MakeLifter(self):
205+
Q = self.lifter_coef
206+
I = np.arange(self.num_ceps)
207+
lifter = 1.0 + 0.5 * Q * np.sin(np.pi * I / Q)
208+
return lifter
209+
210+
#
211+
# MFCCを計算する
212+
#
213+
def ComputeMFCC(self, waveform):
214+
# FBANKおよび対数パワーを計算する
215+
fbank, log_power = self.ComputeFBANK(waveform)
216+
217+
# DCTの基底行列との内積により,DCTを実施する
218+
mfcc = np.dot(fbank, self.dct_matrix.T)
219+
220+
# リフタリングを行う
221+
mfcc *= self.lifter
222+
223+
# MFCCの0次元目を,前処理をする前の波形の対数パワーに置き換える
224+
mfcc[:,0] = log_power
225+
226+
return mfcc
227+
228+
#
229+
# メイン関数
230+
#
231+
if __name__ == "__main__":
232+
233+
#
234+
# 設定ここから
235+
#
236+
237+
# 各wavファイルのリストと特徴量の出力先
238+
train_small_wav_scp = '../data/label/train_small/wav.scp'
239+
train_small_out_dir = './mfcc/train_small'
240+
train_large_wav_scp = '../data/label/train_large/wav.scp'
241+
train_large_out_dir = './mfcc/train_large'
242+
dev_wav_scp = '../data/label/dev/wav.scp'
243+
dev_out_dir = './mfcc/dev'
244+
test_wav_scp = '../data/label/test/wav.scp'
245+
test_out_dir = './mfcc/test'
246+
247+
# サンプリング周波数 [Hz]
248+
sample_frequency = 16000
249+
# フレーム長 [ミリ秒]
250+
frame_length = 25
251+
# フレームシフト [ミリ秒]
252+
frame_shift = 10
253+
# 低周波数帯域除去のカットオフ周波数 [Hz]
254+
low_frequency = 20
255+
# メルフィルタバンクの数
256+
num_mel_bins = 23
257+
# MFCCの次元数
258+
num_ceps = 13
259+
# ディザリングの係数
260+
dither=1.0
261+
262+
# 乱数シードの設定(ディザリング処理結果の再現性を担保)
263+
np.random.seed(seed=0)
264+
265+
# 特徴量抽出クラスを呼び出す
266+
feat_extractor = FeatureExtractor(sample_frequency=sample_frequency,
267+
frame_length=frame_length, frame_shift=frame_shift,
268+
num_mel_bins=num_mel_bins, num_ceps=num_ceps,
269+
low_frequency=low_frequency, dither=dither)
270+
271+
# wavファイルリストと出力先をリストにする
272+
wav_scp_list = [train_small_wav_scp, train_large_wav_scp, dev_wav_scp, test_wav_scp]
273+
out_dir_list = [train_small_out_dir, train_large_out_dir, dev_out_dir, test_out_dir]
274+
275+
# 各セットについて処理を実行する
276+
for (wav_scp, out_dir) in zip(wav_scp_list, out_dir_list):
277+
print('Input wav_scp: %s' % (wav_scp))
278+
print('Output directory: %s' % (out_dir))
279+
280+
# 特徴量ファイルのパス,フレーム数,次元数を記したリスト
281+
feat_scp = os.path.join(out_dir, 'feats.scp')
282+
283+
# 出力ディレクトリが存在しない場合は作成する
284+
os.makedirs(out_dir, exist_ok=True)
285+
286+
# wavリスト,特徴量リストをそれぞれ読み込み,書き込みモードで開く
287+
with open(wav_scp, mode='r') as file_wav, \
288+
open(feat_scp, mode='w') as file_feat:
289+
# wavリストを1行ずつ読み込む
290+
for line in file_wav:
291+
# 各行には,発話IDとwavファイルのパスがスペース区切りで記載されているので,
292+
# split関数を使ってスペース区切りの行をリスト型の変数に変換する
293+
parts = line.split()
294+
# 0番目が発話ID
295+
utterance_id = parts[0]
296+
# 1番目がwavファイルのパス
297+
wav_path = parts[1]
298+
299+
# wavファイルを読み込み,特徴量を計算する
300+
with wave.open(wav_path) as wav:
301+
# サンプリング周波数のチェック
302+
if wav.getframerate() != sample_frequency:
303+
sys.stderr.write('The expected sampling rate is 16000.\n')
304+
exit(1)
305+
# wavファイルが1チャネル(モノラル)データであることをチェック
306+
if wav.getnchannels() != 1:
307+
sys.stderr.write('This program supports monaural wav file only.\n')
308+
exit(1)
309+
310+
# wavデータのサンプル数
311+
num_samples = wav.getnframes()
312+
313+
# wavデータを読み込む
314+
waveform = wav.readframes(num_samples)
315+
316+
# 読み込んだデータはバイナリ値(16bit integer)なので,数値(整数)に変換する
317+
waveform = np.frombuffer(waveform, dtype=np.int16)
318+
319+
# MFCCを計算する
320+
mfcc = feat_extractor.ComputeMFCC(waveform)
321+
322+
# 特徴量のフレーム数と次元数を取得
323+
(num_frames, num_dims) = np.shape(mfcc)
324+
325+
# 特徴量ファイルの名前(splitextで拡張子を取り除いている)
326+
out_file = os.path.splitext(os.path.basename(wav_path))[0]
327+
out_file = os.path.join(os.path.abspath(out_dir), out_file + '.bin')
328+
329+
# データをfloat32形式に変換
330+
mfcc = mfcc.astype(np.float32)
331+
332+
# データをファイルに出力
333+
mfcc.tofile(out_file)
334+
# 発話ID,特徴量ファイルのパス,フレーム数,次元数を特徴量リストに書き込む
335+
file_feat.write("%s %s %d %d\n" %(utterance_id, out_file, num_frames, num_dims))
336+

0 commit comments

Comments
 (0)