研究では異常音検知について取り組んでいます。
なので、音について考えることが多いのですが、音の前処理って結構複雑なんですよね。
スペクトログラムやケプストラム、パワーなのかデシベルなのか、フィルタバンクはどうするか、など様々な手法があって、何をニューラルネットワークの入力にしてあげればよいのか、どんな正規化をしてあげればいいのか、考えるべきポイントがたくさんあります。
この記事では、どのタイミングで正規化をしてあげることが良さそうなのかという点について「定性的」に調べてみました。
(実際は、実験もしたのですが、タスクによって傾向が全く違ったので、初手何が良さそうかという、定性的な情報を提供するということでお許しください。やってみないとわからんのです(´;ω;`))
調べたいこと
ログメルスペクトログラムを入力に受け取るモデルのデータXを作成する際に、Xの正規化はいつするのが良いのか問題について調べる
ただし、実際にモデルの計算をするのではなく、データの分布をみて定性的に評価をする
実験
- 生音をログメルスペクトログラムに変換
- 正規化した生音をログメルスペクトログラムに変換
- 生音をログメルスペクトログラムに変換したものを正規化する
この3つの条件で比較をしてみました。(フィルタバンクもかかっています)
実験のコードです。
生音を正規化
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 4))
plt.subplot(2, 1, 1)
plt.plot(wave)
plt.ylabel("Power", size=16)
plt.xlabel("Time", size=16)
plt.title("Original", size=16)
norm_wave = (wave -wave.mean()) / wave.std()
plt.subplot(2, 1, 2)
plt.plot(norm_wave)
plt.ylabel("Power", size=16)
plt.xlabel("Time", size=16)
plt.title("Wave Normalize", size=16)
plt.tight_layout()
plt.savefig("wave.png")



これは、単純に正規化しただけです。生音では分散が小さかったので、正規化により
Powerのオーダーが100倍になったことがわかります。
スペクトログラムに変換
import numpy as np
import librosa
def logmelfilterbank(
audio,
sampling_rate,
fft_size=1024,
hop_size=256,
win_length=None,
window="hann",
num_mels=80,
fmin=None,
fmax=None,
eps=1e-10,
):
"""Compute log-Mel filterbank feature.
Args:
audio (ndarray): Audio signal (T,).
sampling_rate (int): Sampling rate.
fft_size (int): FFT size.
hop_size (int): Hop size.
win_length (int): Window length. If set to None, it will be the same as fft_size.
window (str): Window function type.
num_mels (int): Number of mel basis.
fmin (int): Minimum frequency in mel basis calculation.
fmax (int): Maximum frequency in mel basis calculation.
eps (float): Epsilon value to avoid inf in log calculation.
Returns:
ndarray: Log Mel filterbank feature (#frames, num_mels).
"""
# get amplitude spectrogram
x_stft = librosa.stft(
audio,
n_fft=fft_size,
hop_length=hop_size,
win_length=win_length,
window=window,
pad_mode="reflect",
)
spc = np.abs(x_stft).T # (#frames, #bins)
# get mel basis
fmin = 0 if fmin is None else fmin
fmax = sampling_rate / 2 if fmax is None else fmax
mel_basis = librosa.filters.mel(sampling_rate, fft_size, num_mels, fmin, fmax)
return np.log10(np.maximum(eps, np.dot(spc, mel_basis.T)))
mel = logmelfilterbank(
wave,
sampling_rate=16000,
hop_size=256,
fft_size=1024,
window="hann",
num_mels=128,
fmin=50,
fmax=8000,
)
mel_norm = logmelfilterbank(
norm_wave,
sampling_rate=16000,
hop_size=256,
fft_size=1024,
window="hann",
num_mels=128,
fmin=50,
fmax=8000,
)
mel_norm_norm = (mel - mel.mean(axis=0)) / mel.std(axis=0)
結果と分布を表示
plt.figure(figsize=(16, 8))
plt.subplot(2, 3, 1)
plt.imshow(mel.T, aspect="auto")
plt.xlabel("time", size=16)
plt.ylabel("frequency", size=16)
plt.title("Original", size=16)
plt.subplot(2, 3, 2)
plt.imshow(mel_norm.T, aspect="auto")
plt.xlabel("time", size=16)
plt.ylabel("frequency", size=16)
plt.title("Wave Normalize", size=16)
plt.subplot(2, 3, 3)
plt.imshow(mel_norm_norm.T, aspect="auto")
plt.xlabel("time", size=16)
plt.ylabel("frequency", size=16)
plt.title("LogMel Normalize", size=16)
plt.colorbar()
plt.subplot(2, 3, 4)
_=plt.hist(mel.flatten(), bins=50)
plt.title("Original", size=16)
plt.subplot(2, 3, 5)
_=plt.hist(mel_norm.flatten(), bins=50)
plt.title("Wave Normalize", size=16)
plt.subplot(2, 3, 6)
_=plt.hist(mel_norm_norm.flatten(), bins=50)
plt.title("LogMel Normalize", size=16)
plt.tight_layout()
plt.savefig("hist.png")



結果&考察
生音を正規化
左が生音です。
真ん中が生音を正規化したものです。
生音を正規化したものは、生音の特徴を保持したままデータの分布がいい感じに0近辺に集まっていることがわかります。
スペクトログラムを正規化
次に、生音と生音をログメルスペクトログラムに変換したものを見ます。
分布だけをみれば0近辺に集まっていますが、スペクトログラムの雰囲気が大きく変わってしまっています。
特に、周波数軸方向の特徴は消えてしまったように見えます。(その軸で正規化したので当然ですが…)
以上から、音の正規化処理の初手として良さそうなものは生音を正規化したものをスペクトログラムに変換して入力をする方法だと考えいます。
処理自体も簡単なので、データセット全体から統計量を計算するコードも煩雑にならないのがよい点かなと思っています。
また、他人に説明をするときになぜここで正規化したのかを説明しやすいのも利点なのかなと思います。
最後に
この記事では、音の正規化について調べました。
正規化処理については、正直やってみないとわからない部分が大きいです。
勘所がわかるようになればある意味一人前なのかもしれませんね。
上では、生音を正規化したものがよいと言っていますが、ある論文では前処理としてスペクトログラムを正規化したと書いてあるものもありました。
ただし、このときは、データセット全体のスペクトログラムの統計量は計算せずに、各スペクトログラムを周波数方向に正規化する処理をしていました。
これは、おそらく、データセットの時点で情報をつぶすことで汎化性能を上げる狙いがあるのではないかと考えています。
イメージとしては、バッチノーマライゼーションに近いと考えています。
あとがき
最近では、スペクトログラムに変換をすること自体がヒューリスティックなので、生音をそのままニューラルネットワークに入力にすればよいのではないかという主張も見かけます。
新しい変換方法として、Wavegramも提案されていました。
スペクトログラムとWavegramを組み合わせることでタグ付けタスクでSOTAもとっていました。
何がいいのかは、タスクによるという話ですね…
新しい情報を仕入れていかないとです!
タイピングもままならない完全にプログラミング初心者から
たった二ヶ月で
応用も簡単にできる…!!
という状態になるまで、一気に成長させてくれたオススメのプログラミングスクールをご紹介します!


