今回は線形周波数ケプストラム係数(Linear-Frequency Cepstrum Coefficients)という音声信号処理の特徴量についての紹介。
このアイデアはMel, Linear, and Antimel Frequency Cepstral Coefficients in Broad Phoneticという論文が最初の発案です。
ここあたりの研究をしてる方ならよく使っているであろう特徴量に
メル周波数ケプストラム係数(Mel-Frequency Cepstrum Coefficients)というものがあります。
現時点ではMFCCがよく使われておりLFCCはあまり活躍していません。
MFCCの解説は人工知能に関する断創録さんの記事が非常に詳しいので割愛しますが、概説しますと
MFCCの抽出手順は
- プリエンファシスフィルタで波形の高域成分を強調する
- 窓関数をかけた後にFFTして振幅スペクトルを求める
- 振幅スペクトルにメルフィルタバンクをかけて圧縮する
- 上記の圧縮した数値列を信号とみなして離散コサイン変換する
- 得られたケプストラムの低次成分がMFCC
に分けられます。
そして、今回紹介するLFCCは3.の部分が少し違うだけです。
それだけで認識精度があがるなら儲けものですよね?
今回はpythonによる実装方法も紹介しますので、ぜひみなさんも使ってほしいです。
では早速実装方法を紹介したいと思いますが、基本的にはMFCCの処理と同じです。
ですので3.の部分だけMFCCと比較しながら実装方法を紹介したいと思います。
他の部分は先ほども紹介したこちらの記事を参考にしてください。非常に分かりやすいです。
では3.の違いを見ていきます。
まず、メルフィルタバンクとはメル尺度という人間の音声知覚を反映した周波数軸を使用したフィルタを指します。
特徴は低周波ほど間隔が狭く、高周波ほど間隔が広くなっている点です。
詳しい数式などはリンクを参考にしてもらって実装に移ります。
まず周波数をメル尺度に変換する関数を定義します。
def hz2mel(f):
"""Hzをmelに変換"""
return 1127.01048 * np.log(f / 700.0 + 1.0)
逆も用意
def hz2mel(f):
"""Hzをmelに変換"""
return 1127.01048 * np.log(f / 700.0 + 1.0)
メルフィルタバンク作成
def melFilterBank(fs, nfft, numChannels, fmax, fmin):
melmax = hz2mel(fmax)-hz2mel(fmin)
# 周波数解像度(周波数インデックス1あたりのHz幅)
df = fs / nfft
# 周波数インデックスの最大数
nmax = int(fmax / df)
nmin = int(fmin / df)
# メル尺度における各フィルタの中心周波数を求める
dmel = melmax / (numChannels)
melcenters = np.arange(hz2mel(fmin), hz2mel(fmax), dmel)
# 各フィルタの中心周波数をHzに変換
fcenters = mel2hz(melcenters)
# 各フィルタの中心周波数を周波数インデックスに変換
indexcenter = np.round(fcenters / df)
# 各フィルタの開始位置のインデックス
indexstart = np.hstack(([nmin], indexcenter[0:numChannels - 1]))
# 各フィルタの終了位置のインデックス
indexstop = np.hstack((indexcenter[1:numChannels], [nmax]))
filterbank = np.zeros((numChannels, int(nfft/2)))#要素0のnumChannels行nmax列の配列作成
for c in np.arange(0, numChannels):
# 三角フィルタの左の直線の傾きから点を求める
increment= 1.0 / (indexcenter[c] - indexstart[c])
for i in np.arange(indexstart[c], indexcenter[c]):
i = int(i)
filterbank[c, i] = (i - indexstart[c]) * increment
# 三角フィルタの右の直線の傾きから点を求める
decrement = 1.0 / (indexstop[c] - indexcenter[c])
for i in np.arange(indexcenter[c], indexstop[c]):
i = int(i)
filterbank[c, i] = 1.0 - ((i - indexcenter[c]) * decrement)
return filterbank, fcenters
こちらのfilterbankをプロットすると
for c in np.arange(0, numChannels):
plot( filterbank[c])
show()
こんな感じで、使いたい周波数領域のフィルタを作成できます。
確かに始まりの方がギュっとしています。
このフィルタの範囲は引数のfmin~fmaxで設定します。
このギザギザの数はnumChannelsで決めます。
fsは使用する音源に合わせ、nfftも適切な値を決めてください。
図はfs=9600,nfft=16384,numChannels=20,fmin=18000,fmax=48000に設定しています。
これが通常のMFCCのメルフィルタバンク作成の実装です。
では、LFCCはといいますと
hz2melとmel2hzは不要でフィルタバンク作成関数は以下の通りです。
def FilterBank(fs, nfft, numChannels, fmax, fmin):
df = fs / nfft
# 周波数インデックスの最大数
nmax = int(fmax / df)
nmin = int(fmin / df)
# メル尺度における各フィルタの中心周波数を求める
dmel = (fmax-fmin) / (numChannels+1)
fcenters = np.arange(int(fmin+dmel), fmax, dmel)
# 各フィルタの中心周波数を周波数インデックスに変換
indexcenter = np.round(fcenters / df)
# 各フィルタの開始位置のインデックス
indexstart = np.hstack(([nmin], indexcenter[0:numChannels - 1]))
# 各フィルタの終了位置のインデックス
indexstop = np.hstack((indexcenter[1:numChannels], [nmax]))
filterbank = np.zeros((numChannels, int(nfft/2)))#要素0のnumChannels行nmax列の配列作成
for c in np.arange(0, numChannels):
# 三角フィルタの左の直線の傾きから点を求める
increment= 1.0 / (indexcenter[c] - indexstart[c])
for i in np.arange(indexstart[c], indexcenter[c]):
i = int(i)
filterbank[c, i] = (i - indexstart[c]) * increment
# 三角フィルタの右の直線の傾きから点を求める
decrement = 1.0 / (indexstop[c] - indexcenter[c])
for i in np.arange(indexcenter[c], indexstop[c]):
i = int(i)
filterbank[c, i] = 1.0 - ((i - indexcenter[c]) * decrement)
return filterbank, fcenters
melFilterbankを流用したので変数名は混同しないように注意
違いはindexcenterを線形に配置してあげるだけで、それに伴う微調整で終わりです。
実際にプロットしてみると
違いが分かると思います。
他の部分は同じですので、あとは機械学習にかけてあげれば違いが分かると思います。
僕の実験ではこちらの方が若干精度が上がりました。
というのもMFCCは人間の音声認識のために作られたものです。
ですが、現在の研究では自分でスイープ信号などの音を発信して、その反響音などを機械学習にかけて何かを認識する研究もあります。
その時にMFCCではバランスが悪い感じもします。
ブラックボックスなので何とも言えないですが。。。
皆さんもすぐにできるのでLFCCを試してみてください。